| ## Charlie Calvert's C++ Builder Unleashed # - 28 - Game Programming ## Overview In this chapter and the next, you'll examine multimedia and game objects. In particular, you will see the following: A simple two-dimensional graphics engine that uses DirectDraw A simple game engine Examples of how to use both objects Various comments on object design The opening portions of a strategy game called Byzantium The graphics engine used in this chapter is implemented on disk both in C++ and in Object Pascal, and it appears to BCB programmers as a set of simple components. The game engine, shown in the next chapter, is implemented in C++, and it appears to the user as a set of objects. In short, the graphics engine consists of components, and the game engine consists of simple objects. You can use all the code found in this book in your own programs, but you cannot use the art or designs found in this chapter and the next. My objection is to distributing the art in your own programs. You are free to use the art in your own home or at your company, but you cannot distribute it to others in any form. You can use the game engine to build your own games, but it must be distinct from the game you find in the next chapter. The world must be different, and so should the art. I hope that many readers are specifically interested in developing and using game engines. If you're not interested in that subject, I still think that you will find these chapters among the most interesting in the book. In particular, in these pages you will see how to create complex sets of objects and use them in real-world programs that have to perform complex tasks. The DirectX technology shown in these chapters can be used in a wide variety of applications. For example, you can use the graphics engine shown here to create the following: Games Simulations Presentations Advanced graphical features of all kinds Besides describing the raw graphics tools in this chapter, I also show you how to create complex object hierarchies, how to plan and design the functionality of an object properly, how to divide groups of objects into specific engines, and how to get the engines to work together. The overriding theme of these chapters is the importance of object design. You have to learn how to create objects that are reusable and how to design object hierarchies that are simple enough to meet the needs of real programmers. In the end, everything comes down to design. If you design the right kinds of objects, you can accomplish almost anything. Furthermore, as you will see, the judicious use of objects can help you dig yourself out of almost any hole. Objects are the Holy Grail of contemporary programming, and if you understand how to use them correctly, they will help you complete project after project on time. Just understanding the basics of object technology is not enough. Just knowing about polymorphism, inheritance, and encapsulation is not enough. You also need to know how to design objects that you can use and that other people can use. This section of the book is about designing these kinds of objects. Of course, on the way, you will also have the chance to engage in a good deal of fun with graphics, games, and multimedia. ## Game Resources Game programming is more complicated than most standard Windows programming tasks for many reasons. The root of this complexity is the need to use special graphics tools such as DirectX. Another complication that many first-time game programmers underestimate involves creating art. Many books are available on game programming and on producing art for games. Two that I have found useful are Tricks of the Game Programming GurusI?, LaMothe, Ratcliff,et al., Sams Publishing The Ultimate Game Developers Sourcebook, Ben Sawyer, Coriolis Group Books. You will also need a paint program. I find that the inexpensive shareware program called Paint Shop Pro meets most of my needs, though many other powerful programs such as Fractal Paint (`www.fractal.com`) are available. Here's the contact information for Paint Shop Pro: JASC, Inc. P.O. Box 44997 Eden Prairie, MN 55344 930-9171 `` `www.jasc.com``` Other key paint programs that I use often include TrueSpace (`www.caligari.com`) and VistaPro from Virtual Reality Laboratories in San Luis Obispo. TrueSpace allows you to create three-dimensional objects, and VistaPro allows you to create landscapes. The background scenes that show rolling hills, mountains, and trees in the figures for this chapter and the next were created in VistaPro (Virtual Reality Laboratories in San Luis Obispo, CA, 805-545-8515, e-mail: `VRLI@aol.com`, WWW:`` `http://www.romt.com`). (See the files called `Backgrd1.bmp` and `Backgrd2.bmp` on the CD that comes with this book for examples of VistaPro files that have gone through a color reduction so that they could fit on a 256-color palette.) You might find additional game components, links, or code available at these sites:
    http://www.silvercrk.com/directx/

    http://www.spinlogic.com/GameDev/

    http://users.aol.com/charliecal
Many other Web sites are of interest to game developers, but you can find links to most of them from the sites listed here. On CompuServe, type `GO GAMEDEV` to find the game developers' forum. As I stated previously, game programming is an extremely complex undertaking. The biggest mistake you can make is to try to create your own tools from scratch. Use the tools included in this book to get some sense of what you can gain from a graphics engine and a set of gaming tools. Then go out and search the Web and your local bookstores for ready-made graphics and game engines. Build your games using these tools; don't try to create your own tools from scratch unless you're sure, double-sure, and then triple-sure you know what you're doing and why. If you have never built a game before, then don't even consider building one from scratch. Build your first game with someone else's engine. Then, after you understand something about the tools that are available, you might finally be in a position to consider creating some of your own tools. Even then, however, I would still recommend turning around and using someone else's tools rather than trying to create your own. The Ultimate Game Developers Sourcebook, mentioned previously, offers a great deal of information on finding third-party game engines. ## Where We're Headed: Object Overview In this part of the book, you'll create two sets of objects. Using the first set of objects, you can encapsulate DirectX multimedia functionality inside easy-to-use components. The second set of objects allows you to create games, simulations, or multimedia tools using the game engine. The classic mistake to make in this process is to find or create the objects that give you access to the multimedia and DirectX interfaces and to then think you're ready to create a program. This error is fatal--and very common. Suppose that you have a set of objects that give you full access to DirectDraw. The mistake many programmers make is to assume that they can directly use these objects when creating a game or simulation. Indeed, in some cases, that may be possible. However, most of the time you need to create a second set of objects tailored specifically for games or simulations. In particular, you need a `TGameEngine` object. Furthermore, this object is not necessarily a direct descendant of any of your graphics-based objects. It may use them via aggregation but probably does not descend directly from them. The same applies for simulations: You need a `TSimulation` object that uses your graphics objects but presents a different interface to the user. A game object, for example, needs items such as `TPlayer`, `TBoard`, `THero`, `TScene`, and so on. Your graphic objects don't know anything about those kinds of objects. To get a handle on this issue, you need to think in terms of specific problem domains. One problem domain involves using DirectDraw to create fast graphics. A second problem domain involves creating a game engine, and a third problem domain involves creating a specific game. You need to draw firm lines between these domains so that the complexity of one problem domain does not creep into another domain. As a rule, you should not allow any direct calls to your graphics objects by the users of your `TGameEngine` object. `TGameEngine` calls the graphics objects, but it does so via private interfaces that `TGameEngine` users can never see. NOTE: An example might be helpful here. In DirectX, you can write text to the screen using device contexts. You get the DC for a device, copy objects into it, and then use `TextOut` to write to the screen. Having the game engine use these same objects is probably not wise. Instead, you should hide the underlying technology and provide a routine with a name such as `WriteXY` or `WriteLn` or `printf` or `Say` that will let you write to the screen. Internally, your implementation of `WriteXY` will use device contexts, but that fact should be hidden from the user. This will allow you to switch your game from Windows to DOS to the Mac without having to change its code. Only the interface to the graphics engine changes, but your game code remains untouched. One way to help design these objects correctly is to ensure that a user of `TGameEngine` would have to do nothing more than recompile if you were to switch your game engine technology from one set of graphics objects to another. In other words, the users of `TGameEngine` should not have to know anything about the tools you use to create fast graphics. You can tell them, "Hey, I use DirectDraw," or "I use WinG," but the actual technology involved should not matter to them in the sense that their code will still compile if you switch the background engine. Users of `TGameEngine` should not have to know whether `TGameEngine` uses DirectDraw or WinG. Users of `TGameEngine` never call the graphics objects directly, and they don't even have to conceive of the fact that such a thing as an `IDirectDraw` object exists. Obviously, one of the important benefits of this system is that you can change from WinG to DirectDraw without having to rewrite your game or game engine. But this capability is not the primary benefit that interests me. What I like about this system is that it creates domains for problems. If I'm working with the `TGameEngine` object and find that it's not drawing things correctly because of some error in the way I access DirectDraw, then I can put the `TGameEngine` object away, open the graphics engine, and find out what is wrong. My game engine and my game cannot be the cause of the problem because they don't talk to the `IDirectDraw` interface. They don't know anything about the `IDirectDraw` interface! Another benefit of this system is that it allows you to improve one part of your code without affecting existing parts of your code. For example, I could drastically rewrite the graphics engine without having to change the way `TGameEngine` operates. In these situations, rewriting the graphics engine without changing its interface is best, but even if you do change it, you can still buffer your game from these changes because it talks to `TGameEngine`, not to the graphics engine. However, the best thing to do is to never change a public interface after you create it. NOTE: I will concede that some of the theory I am describing here is a bit idealized. In practice, I rarely achieve precisely the degree of object independence described in the preceding paragraphs. However, the closer I can get to it, the better off I am. Aiming as high as possible is always best, thereby limiting as much as possible the amount of trouble you will have if your architecture needs to be upgraded. Furthermore, the process of learning to design good objects is incremental at best. The first time I tried to create a complex set of objects, I failed miserably. The second time, I took to heart some of the ideas I am describing here and came closer to achieving my goal. The third time out I made yet fewer gross design errors, and so on, till finally I was starting to produce complex object hierarchies that could actually be used and maintained. ## Creating Public Interfaces The last paragraph of the preceding section introduces a second key rule of object-oriented design. Creating problem domains is the most important step, but a second crucial condition is creating public interfaces that have little to do with your underlying technology. When you're creating the public interface for the graphics engine, you should take off your `IDirectDraw` expert hat and put on one that helps you pretend you are the user of this object. What will a user of this object want to see? A user doesn't want to hear about the nitty-gritty of using `IDirectDraw`; instead, he or she wants to be able to work with broad concepts such as `TBackground`, `TSprite`, and `TWindow`. ## Managing Complexity: Create Humble Objects Before going further, I want to talk just a little more about limiting the problem domain. One of the most important jobs that objects can do for you is to help manage complexity. In the first chapter of this book, I stressed the importance of being humble when you program. Here, that theme can really start to pay off. The worst mistake you can make as a programmer is to decide "Hey, I'm so smart, so cool, that I can make this object stunningly complex and efficient and still be able to use it." In the process, you might end up continually collapsing one object into another and continually finding fast, clever ways to get tasks done in the fewest possible moves. All this cleverness is good and well, if you can simultaneously produce objects that you and others can use and maintain. Putting all the logic into one set of objects has the following problems: Any hope of code reuse is obliterated. If one small set of objects forms the whole game, how can you separate the reusable part of the code for a second game that has similar characteristics? Maintaining it is a nightmare. If something goes wrong anywhere in the game, you have no way of localizing a problem and testing it separately from the rest of the application. If all you have are a few big, complex objects, when something goes wrong, all the lines of code in your program are suspect, and you cannot separate out the code you need to test and repair. This monumental problem is likely to sink most projects completely. You have to be able to break down a program into its various components and test each part separately. Teaching someone how to work with the code for a game is extremely hard if he or she has to understand the entire game to be able to work on any part of it. Understanding one huge, complex object is difficult. The paradigm is innately complex. You have a much simpler task getting someone up to speed on some code if you can say, "Here, we need to make these changes to this 500-line object. Please get to work right away; time is of the essence." The nightmare scenario sounds like this: "Welcome aboard. We have 200,000 lines of code here, and we need to make some changes to it. Please spend the next six months becoming familiar with our code base, and then I would like you to make some changes to a few small portions of it. Tell me when you're up to speed." Putting all the logic in clearly delineated sets of objects has the following advantages, which are the converse of the disadvantages laid out in the preceding three points. Code reuse is supported. A well-designed graphics engine can be reused in almost any graphics-based game. A subset of those objects can be used in virtually any multimedia venture that requires DirectDraw. The same is true with the `GameEngine` code. It's all in one place and can be reused. Conversely, the code that is specific to Byzantium is all in one place. Maintaining it is easy. If something goes wrong in the code, you can isolate the problem in a few distinct objects that you can test separately. Problems in one part of the code don't automatically percolate into other, related domains. Instead, they are isolated in one section of the code. This problem is like a leak in a submarine: If one section of the ship is damaged, you can seal it off from the rest of the ship. It won't affect the whole program; it is isolated in one area. You can then concentrate on fixing that area without worrying about the ramifications of those repairs on the rest of the program. Understanding it is easy. You know exactly what is going on in the graphics engine components because the problem domain is manageable. You can explain the basic structure of the program to someone in five minutes because each part plays a distinct, easy-to-understand role. The theme underlying all these points is humility. To be a good programmer, you have to admit that a certain level of complexity simply overwhelms you. Your only bulwark against that confusion is to break down the project into manageable chunks that you can understand. Humble programmers produce real programs. Hotshots are going to produce fantastic programs Real Soon Now. I know that's true because those hotshots have been telling me about their program ideas for years. ## The DirectX Game Engine In the next few sections of the chapter, I describe a set of components that make up a simple graphics engine. The key components in this graphics engine are described in Table 28.1. #### Table 28.1. The key objects in the graphics engine.
Component | Purpose
`THermes` | Get into and out of DirectX Exclusive mode
`TScene` | Draw the background of a graphics scene
`TSpriteScene` | Draw the background of a graphics scene that supports sprites
`THermesChart` | Create a tiled game scene
After I describe the graphics engine, I will describe the game engine that can be used in conjunction with these objects. ## THermes: Getting Into and Out of Exclusive Mode DirectX is a Microsoft technology that allows you to write high-performance gaming, simulation, and multimedia programs. In this chapter, I focus on the `IDirectDraw` interface, which is a portion of DirectX that gives you the ability to write directly to the video buffer. The first thing you find when working with DirectX is that, to take full advantage of it, you need to switch into a special video mode called Exclusive mode. For the purposes of this book, this mode has a 640x480 screen dimension and 256 colors. Actually, other screen sizes and color resolutions are available to you as a DirectX programmer, but `THermes` gives you access only to this one screen mode. The first object in the graphics engine is designed to switch you into and out of this mode. The class declaration for this object is shown in Listing 28.1. You can find the full source for the code shown here in a file called `Mercury2.cpp` in the `Utils` directory on the CD that accompanies this book. The `Mercury` unit relies on `Creatures.pas`, which ships with this book, and `DDraw.h`, which ships in the included directory with BCB. You will also find a Pascal version of the `Mercury` unit in the `Units` subdirectory on the CD that accompanies this book. NOTE: To find some Web sites of interest to DirectX programmers and full Pascal translations of DirectX 3.X headers, go to the following: http://www.dkw.com/bstone/
When you're looking at this code, keep these points in mind: I do not quote the entire source until the end of the chapter. Throughout the main body of this chapter, I quote only the class declarations, one at a time as needed so that you can use them to grasp the structure of the portion of code currently under discussion. At the end of the chapter, I quote the entire object. You can install all the key objects in these units as components and use them directly in BCB, just as you would use any of the other components discussed in this book. You don't really have to understand this code, but you do need to know how to use the components created when these objects are installed on the Component Palette. #### Listing 28.1. Mercury2.cpp contains logic for handling sprites, backgrounds, and DirectX. The key object in this file is THermes.
    class THermes : public Classes::TComponent

    {

      typedef Classes::TComponent inherited;

      friend THermesChart;

      friend TDraw;

    private:

      bool FActive;

      Creatures1::TFileCreatureList* FCreatureList;

      HWND FHandle;

      bool FTimerOdd;

      int FTimerInterval;

      bool FExclusive;

      Classes::TNotifyEvent FPaintProc;

      TScene* FScene;

      bool FUseTimer;

      bool FFirstTime;

      IDirectDraw* FDirectDraw;

      IDirectDrawSurface* FBackSurface;

      IDirectDrawClipper* FClipper;

      IDirectDrawSurface* FPrimarySurface;

      bool __fastcall CreatePrimary(void);

      void __fastcall DDTest(long hr,  System::AnsiString S);

      void __fastcall InitBaseObjects(void);

      bool __fastcall MakeItSo(long DDResult);

      void __fastcall SetScene(TScene* Scene);

      bool __fastcall SetUpBack(void);

      protected:

      void __fastcall DrawBitmaps(void);

      virtual long __fastcall RestoreSurfaces(void);

    public:

      __fastcall virtual THermes(Classes::TComponent* AOwner);

      __fastcall virtual ~THermes(void) {}

      void __fastcall EndExclusive(void);

      void __fastcall ErrorEvent( System::AnsiString S);

      void __fastcall Flip(void);

      virtual void __stdcall InitObjects(void);

      virtual void __fastcall Run(void);

      __property bool Active = {read=FActive, write=FActive, nodefault};

      __property IDirectDrawSurface* BackSurface =

         {read=FBackSurface, write=FBackSurface, nodefault};

      __property Classes::TNotifyEvent OnDrawBitmap =

         {read=FPaintProc, write=FPaintProc};

    __published:

      __property Creatures1::TFileCreatureList* CreatureList =

          {read=FCreatureList, write=FCreatureList, nodefault};

      __property bool Exclusive = {read=FExclusive, write=FExclusive, nodefault};

      __property TScene* Scene = {read=FScene, write=SetScene, nodefault};

      __property int TimerInterval =

        {read=FTimerInterval, write=FTimerInterval, nodefault};

      __property bool UseTimer = {read=FUseTimer, write=FUseTimer, nodefault};
`};` To install `THermes` and the other graphics engine objects, choose Component | Install. Click the Add button, and then browse the `Units` directory from the CD that accompanies this book. Install both `Creatures1.pas` and `Mercury2.cpp`. To use `THermes`, start a new project, drop a `THermes` object on it, and create an `OnKeyDown` handler that closes the form if any key is pressed. The code for such a project is shown in Listings 28.2 and 28.3. #### Listing 28.2. The HermesTest1 project shows how to use THermes. The header for the main unit is shown here.
    ///////////////////////////////////////

    // Main.h

    // Testing the THermes object

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef MainH

    #define MainH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include "..\..\utils\Mercury2.h"

    #include <vcl\Menus.hpp>

    class TForm1 : public TForm

    {

    __published:

      THermes *Hermes1;

      TMainMenu *MainMenu1;

      TMenuItem *Run1;

      void __fastcall FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift);

      void __fastcall Run1Click(TObject *Sender);

    private:

      void __fastcall MyExceptions(TObject *Sender, Exception *E);

    public:

      __fastcall TForm1(TComponent* Owner);

    };

    extern TForm1 *Form1;
`#endif` #### Listing 28.3. The source for the main unit in the HermesTest1 project.
    ///////////////////////////////////////

    // Main.cpp

    // The TestHermes project tests the THermes component

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma link "Mercury2"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

      Application->OnException = MyExceptions;

    }

    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,

      TShiftState Shift)

    {

      if ((Shift.Contains(ssAlt)) && (Key == `E'))

        throw Exception("Some Exception");

      else

        if (!Shift.Contains(ssAlt))

          Close();

    }

    void __fastcall TForm1::Run1Click(TObject *Sender)

    {

      Hermes1->Run();

    }

    void __fastcall TForm1::MyExceptions(TObject *Sender,

      Exception *E)

    {

      Hermes1->EndExclusive();

      ShowMessage(E->Message);
`}` You should note that this project probably will not run correctly unless you add `Creatures.pas` to it, as shown in the `USEUNIT` statement from the project source:
    //--------------------------------------------------------------------------

    #include <vcl\vcl.h>

    #pragma hdrstop

    //--------------------------------------------------------------------------

    USEFORM("Main.cpp", Form1);

    USERES("HermesTest1.res");

    USEUNIT("..\..\Units\creatures1.pas");

    //--------------------------------------------------------------------------

    WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

    {

      try

      {

        Application->Initialize();

        Application->CreateForm(__classid(TForm1), &Form1);

        Application->Run();

      }

      catch (Exception &exception)

      {

        Application->ShowException(&exception);

      }

      return 0;

    }

    //--------------------------------------------------------------------------
Run the project once with `Exclusive` set to `False`. Click the Run button once to switch on DirectX. In this mode, you should see the color of the main form change, but nothing else very special will happen. Go back into design mode, and check once to make sure that you have not placed any components on the main form other than the menu and the `THermes`. The rest of the form must be free of visual controls so that you can detect keystrokes directly on the surface of the form. Set `Exclusive` to `True` and run the program again. Click the Run menu item, and the whole screen should go blank as you switch into `Exclusive` mode. To get out of the mode, simply press any key. If you want, you can change the code for the `OnKeyDown` event:
    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,

      TShiftState Shift)

    {

      if ((Shift.Contains(ssAlt)) && (Key == `X'))

        Close();

    }
With this code in place, you will be able to press Alt+Tab to move away from the main window of the program and switch out of `Exclusive` mode. You can then press Alt+Tab to move back to the program and press Alt+X to exit. When you're pressing these keys, the actual picture drawn on your screen when in `Exclusive` mode is undefined because `THermes` does not control the output in `Exclusive` mode; it controls only the act of entering and exiting `Exclusive` mode. DirectX allows you to run applications in a windowed mode. That is, you don't have to slip into `Exclusive` mode to run DirectX applications. However, windowed mode is not very good in terms of performance. In fact, you'll find little advantage to a windowed mode DirectX application over a GDI-based standard Windows program. I give you access to windowed mode primarily because it is valuable when you're debugging an application. In short, you should run your DirectX applications in windowed mode while you're debugging them so that you can step through the debugger while viewing your application's output. After you set up your application correctly, switch into Exclusive mode and test it. If an exception happens when you're in `Exclusive` mode, that is a bad thing. In particular, your program will appear to lock up, and you may be forced to reboot. Following the steps outlined in the preceding paragraphs helps you eliminate the possibility that this will happen. However, you cannot be sure that an exception will not be raised in your application. As a result, you might want to override the exception handler for your application in this case:
    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

      Application->OnException = MyExceptions;

    }

    void __fastcall TForm1::MyExceptions(TObject *Sender,

      Exception *E)

    {

      Hermes1->EndExclusive();

      ShowMessage(E->Message);

    }
The constructor for the main form designates a custom routine called `MyExceptions` to handle all the exceptions that occur in this application. Whenever an unhandled exception occurs, it will be passed to this routine. When I get the exception, I call `Hermes1->EndExclusive`, which pops the application out of `Exclusive` mode, therefore making it possible to open the dialog box that shows the error message. NOTE: This system will not do you any good, of course, unless you turn off the Break On Exception option, which you can find by choosing Options | Environment | Preferences. If you are in Exclusive mode, and the IDE tries to take you to the line of code in your application where an exception occurred, your system will appear to lock up. As a rule, I will not show you the underlying Object Pascal code that makes this component work. However, in this one case, I will show you what goes on:
    procedure THermes.EndExclusive;

    begin

      if (FDirectDraw <> nil) then begin

        FDirectDraw.FlipToGDISurface;

        if FExclusive then

          FDirectDraw.SetCooperativeLevel(FHandle, DDSCL_Normal);

      end;

    end;
This code first flips away from DirectX to GDI mode and then cuts out of Exclusive mode by setting the cooperative level to normal. When I take the application into Exclusive mode, I make the same call but with different parameters:
    DDTest(DirectDrawCreate(nil, FDirectDraw, nil), `InitObjects1');

    if not FExclusive then

      Flags := DDSCL_NORMAL

    else

      Flags := DDSCL_EXCLUSIVE or DDSCL_FULLSCREEN;

    DDTest(FDirectDraw.SetCooperativeLevel(FHandle, Flags),'SetCooperativeLevel');

    if FExclusive then begin

      hr := FDirectDraw.SetDisplayMode(640, 480, 8);

      if(hr <> DD_OK) then

        raise EDDError.CreateFmt(`THermes.InitObjects: %d %s',

          [hr, GetOleError(hr)]);
    end;
Notice that my `EndExclusive` method makes no attempt to set the display mode back to the dimensions and bit depth selected by the user in his or her system configuration. Instead, I just leave it to the user to close the application, which will return the screen to its normal resolution. My goal in using `EndExclusive` is just to keep the system from hanging. ## TScene: Drawing a Background After you know how to get into and out of `Exclusive` mode, the next step is to learn how to draw a picture to the screen. Most games consist of many pictures, but I start out showing you how to use a simple object that just displays a single bitmap. For most people, this object is not really very useful, in that there is no point in using DirectX if you want to show only one picture. However, `TScene` serves as a base class for other objects that handle more complex tasks. I will therefore spend a few moments showing you how to use it so that you will understand the class on which the other objects are built. You can find the declaration for the class and some code that uses it in Listings 28.4 through 28.6. Again, I show only the class declarations for the Pascal code and give the complete listings for the C++ code. #### Listing 28.4. The class declaration for the key parts of the TScene and TDraw objects.
    class TDraw;

    class TDraw : public Classes::TComponent

    {

      typedef Classes::TComponent inherited;

      friend THermesTiler;

      friend THermesChart;

      friend TScene;

      friend TSpriteScene;

    private:

      AnsiString FDLLName;

      THermes* FHermes;

      HANDLE FLib;

      IDirectDrawPalette* FPalette;

      int FTransparentColor;

      bool __fastcall CreateDDSurface(IDirectDrawSurface* &DDS,

        System::AnsiString BitmapName, bool UsePalette);

      IDirectDrawSurface* __fastcall CreateSurface(HANDLE Bitmap);

      HANDLE __fastcall GetDib(HANDLE Instance,  System::AnsiString S);

      IDirectDrawPalette* __fastcall LoadPalette(HANDLE Instance,

        const System::AnsiString BitmapName);

    public:

      __fastcall virtual TDraw(Classes::TComponent* AOwner);

      __fastcall virtual ~TDraw(void);

          void __fastcall WriteXY(int X, int Y,  System::AnsiString S);

      __property int TransparentColor =

        {read=FTransparentColor, write=FTransparentColor, nodefault};

    __published:

      __property AnsiString DLLName =

        {read=FDLLName, write=FDLLName};

    };

    class TScene : public TDraw

    {

      typedef TDraw inherited;

      friend THermes;

    private:

      System::AnsiString FBackgroundBitmap;

      Graphics::TColor FBackColor;

      RECT FBackRect;

      tagPOINT FBackOrigin;

      bool FBlankScene;

      bool FShowBitmap;

      IDirectDrawSurface* FWorkSurface;

      Classes::TNotifyEvent FOnSetupSurfaces;

      Classes::TNotifyEvent FOnDrawScene;

    public:

      __fastcall virtual TScene(Classes::TComponent* AOwner);

      virtual void __fastcall DestroyObjects(void);

      virtual void __fastcall DrawScene(void);

      virtual long __fastcall RestoreSurfaces(void);

      virtual void __fastcall SetupSurfaces(System::TObject* Sender);

    __published:

      __property System::AnsiString BackgroundBitmap =

        {read=FBackgroundBitmap, write=FBackgroundBitmap, nodefault};

      __property bool BlankScene = {read=FBlankScene, write=FBlankScene, nodefault};

      __property long OriginX ={read=FBackOrigin.x, write=FBackOrigin.x, nodefault};

      __property long OriginY ={read=FBackOrigin.y, write=FBackOrigin.y, nodefault};

      __property bool ShowBitmap = {read=FShowBitmap, write=FShowBitmap, default=1};

      __property Classes::TNotifyEvent OnDrawScene =

        {read=FOnDrawScene, write=FOnDrawScene};

      __property Classes::TNotifyEvent OnSetupSurfaces =

        {read=FOnSetupSurfaces, write=FOnSetupSurfaces};

      __property Graphics::TColor BackColor =

        {read=FBackColor, write=FBackColor, nodefault};

      __property TransparentColor ;

    public:

      __fastcall virtual ~TScene(void) { }
`};` #### Listing 28.5. The header for the unit from the SceneTest1 project that tests the TScene object.
    ///////////////////////////////////////

    // Main.h

    // Test the TScene object

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef MainH

    #define MainH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include <vcl\Menus.hpp>

    #include "Mercury2.h"

    class TForm1 : public TForm

    {

    __published:

      TScene *Scene1;

      THermes *Hermes1;

      TMainMenu *MainMenu1;

      TMenuItem *Run1;

      void __fastcall Run1Click(TObject *Sender);

      void __fastcall FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift);

    private:

    public:

      __fastcall TForm1(TComponent* Owner);

    };

    extern TForm1 *Form1;
`#endif` #### Listing 28.6. The main form for the unit that tests the TScene object.
    ///////////////////////////////////////

    // Main.cpp

    // Test the TScene object

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma link "Mercury2"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

    }

    void __fastcall TForm1::Run1Click(TObject *Sender)

    {

      Hermes1->Run();

    }

    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,

      TShiftState Shift)

    {

      if ((Shift.Contains(ssAlt)) && (Key == `X'))

        Close();
`}` The program shown here can switch you into `Exclusive` mode and show a picture like the one shown in Figure 28.1. Figure 28.1. The main screen of the TestScenel program in windowed mode. To get started with the `TScene` object, drop both it and the `THermes` object on a form. Use the `Scene` property of `THermes` to connect it to the `TScene` object. Click the `TScene` object, and select a background bitmap by browsing the `Chap28/Media` directory included on the CD that accompanies this book. Select any bitmap that is smaller than or equal to 640x480 and that has 256 colors in it. For example, you might choose `BackGrd2.bmp`. Set the Transparent color to black, which will appear at offset zero on the bitmaps. Set the `BackColor` property of the bitmap to the same shade, and set `BlankScene` to `True`:
    TransparentColor = 0;

    BackColor = clBlack;

    BlankScene = True;
NOTE: `TransparentColor` refers to the color in a bitmap that will be transparent. That is, the areas in the bitmap where this color is present become transparent. You need a transparent color when you want to blit an irregularly shaped bitmap on top of a background bitmap. For example, if you have a picture of a bird that you want to blit onto a picture of the sky, just set the background area in the picture of the bird to the transparent color, and it will not show up when the bird is blitted to the screen. In other words, the picture of the sky will show through the transparent area, so the bird will appear directly against the sky, with no intervening border. You choose the selected transparent color from an offset in the palette of the bitmap you're using. For example, if you set `TransparentColor` to `0`, the first color in the bitmap's palette will be the transparent color. Most good paint programs will show you the palette for a picture. For example, Paint Shop Pro provides this service. If you select a prominently used color from a picture as the transparent color, the portions of the picture with that color in them will look strange at runtime. To avoid this situation, set the `BackColor` property of `TScene` and the `TransparentColor` for the picture to the same shade. Also set `BlankScene` to `True`. This way, the transparent color will look through onto a background that is the same shade as the color you're making transparent. The `BlankScene` property blanks the background of the `TScene` object to the color found in the `BackColor` property. This capability can be useful in the current situation and also when you have a small bitmap that you want to show against a blank window set to some particular color. When you're creating multimedia applications in Windows, the palette of your bitmaps is very important. In particular, you should usually create the same 256-color palette for all your bitmaps. Use a program such as Paint Shop Pro to set up these bitmaps. Most of the bitmaps included in the `Chap28/Media` directory on the CD that comes with this book have a palette designed for pictures that have lots of green fields, brown earth, and a range of skin tones. When you're working with only one bitmap in a scene, all this business about palettes seems unnecessarily complicated. However, after you start adding sprites to the scene, you will be glad that you have some way to set up background colors and to set up `TransparentColor`. I explain more about palettes in the next section of this chapter. The `TScene` object also has a built-in timer that will continually redraw the picture at preset intervals. This capability is not particularly useful in the case of a program that blits only one picture to the screen. However, when you start working with multiple bitmaps, some of which are animated, this capability becomes a key feature. In particular, DirectX allows you to draw to an offscreen buffer, which is then blitted to the screen almost instantly. Using this method is an excellent way to achieve smooth animation. Instead of using a timer, you can also handle the `TApplication::OnIdle` event, and then call `Hermes1->Flip` inside that event handler. The`OnIdle` event will be called whenever the CPU is not otherwise occupied. To get the `TScene` object set up correctly, you should turn on the timer and set the `TimerInterval` property to some reasonable value, such as 250 milliseconds. Setting this property will cause the picture to be refreshed four times a second. This number is too slow for animation, but it works fine for the simple scene created here. In particular, if you press Alt+Tab to move away from the DirectX program, the timer will automatically restore the picture when you press Alt+Tab to move back to the main scene. On the CD that accompanies this book, you will find a program called TestScene1. The source for this program is shown in Listings 28.5 and 28.6. You can use this code as a guide for using the `TScene` object. Remember that `TScene` is not really meant to be used in a real program. It is just a base object from which other, more useful objects can descend. ## Art and Palettes In Windows and DOS games, you almost always work with a 256-color palette. Someday this will change, but for now you should get used to working with these palettes and come to understand them well. An in-depth description of palettes is available in some of the books and Web sites mentioned in the section called "Games Resources" at the beginning of this chapter. Using Paint Shop Pro, you can save a palette to disk as a text file:
    JASC-PAL

    0100

    256

    0 0 0

    128 0 0

    0 128 0

    128 128 0

    0 0 128

    128 0 128

    0 128 128

    192 192 192

    176 136 72

    216 192 160

    ... // Many numbers omitted here...

    240 224 208

    160 160 164

    128 128 128

    255 0 0

    0 255 0

    255 255 0

    0 0 255

    255 0 255

    0 255 255

    255 255 255
Here is how to decode the first few lines of this file:
    JASC-PAL

    0100

    256

    0 0 0
`JASC-PAL` identifies the palette as belonging to Paint Shop Pro. The second line defines the number of entries in the palette in hexadecimal format. The third line has the number of entries in base ten notation. The fourth line is the first member of the palette in RGB format, with all values set to `0`, which is black. Notice that my palette runs through various shades:
    118 102 237

    117 116 240

    129 113 249

    155 110 239

    107 144 140

    115 144 140

    123 136 140

    123 148 140

    111 148 148
Clearly, I'm interested in this area of the palette. Obviously, you don't want to have to work with raw numbers like this. Paint Shop Pro will let you view a palette as an array of colors, as shown in Figure 28.2. Having some kind of tool that will help you design your palette is essential. You should also find books, such as this one and the ones mentioned previously, that include sample palettes. Figure 28.2. Using Paint Shop Pro to view an array of shades that make up a 256-color palette. ## Working with TSpriteScene Now that you know how to show a simple picture in DirectX mode, and now that you know a bit about palettes, you're ready to start working with sprites. The sprite support I have at this time is very rudimentary, as you can see in Listings 28.7 through 28.9. You could, however, use these base sprite classes to create sprites that are more full-featured. You should also check my Web site for updates to this code. #### Listing 28.7. The declarations for the TSpriteScene and TScene classes.
     class TSprite : public Classes::TComponent

    {

      typedef Classes::TComponent inherited;

      friend TSpriteScene;

    private:

      System::AnsiString FBitmap;

      tagPOINT FPosition;

      IDirectDrawSurface* FSurface;

      RECT FRect;

    public:

      bool __fastcall IsHit(int X, int Y);

      __property IDirectDrawSurface* Surface=

          {read=FSurface, write=FSurface, nodefault};

      __property RECT Rect = {read=FRect, write=FRect};

      __published:

      __property System::AnsiString Bitmap={read=FBitmap, write=FBitmap, nodefault};

      __property long XPos = {read=FPosition.x, write=FPosition.x, nodefault};

      __property long YPos = {read=FPosition.y, write=FPosition.y, nodefault};

    public:

      __fastcall virtual TSprite(Classes::TComponent* AOwner)

        : Classes::TComponent(AOwner) { }

      __fastcall virtual ~TSprite(void) { }

    };

    class TSpriteScene : public TScene

    {

      typedef TScene inherited;

    private:

      Classes::TList* FSpriteList;

    protected:

      __fastcall virtual ~TSpriteScene(void);

      virtual void __fastcall SetupSurfaces(System::TObject* Sender);

    public:

      __fastcall virtual TSpriteScene(Classes::TComponent* AOwner);

      virtual void __fastcall DestroyObjects(void);

      void __fastcall AddSprite(TSprite* Sprite);

      virtual void __fastcall DrawScene(void);

      virtual long __fastcall RestoreSurfaces(void);

      __property Classes::TList* SpriteList = {read=FSpriteList, write=FSpriteList, nodefault};

    __published:

      __property TransparentColor;
`};` #### Listing 28.8. The header for the SpriteTest1 program.
    ///////////////////////////////////////

    // Main.h

    // Project: SpriteTest1

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef MainH

    #define MainH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include <vcl\Menus.hpp>

    #include "Mercury2.h"

    class TForm1 : public TForm

    {

    __published:

      THermes *Hermes1;

      TSpriteScene *SpriteScene1;

      TSprite *Sprite1;

      TSprite *Sprite2;

      TMainMenu *MainMenu1;

      TMenuItem *Run1;

      void __fastcall SpriteScene1SetupSurfaces(TObject *Sender);

      void __fastcall Run1Click(TObject *Sender);

      void __fastcall FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift);

    private:

    public:

      __fastcall TForm1(TComponent* Owner);

    };

    extern TForm1 *Form1;
`#endif` #### Listing 28.9. The main form for the TestSprite1 program.
    ///////////////////////////////////////

    // Main.cpp

    // Project: SpriteTest1

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma link "Mercury2"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

    }

    void __fastcall TForm1::SpriteScene1SetupSurfaces(TObject *Sender)

    {

      SpriteScene1->SpriteList->Add(Sprite1);

      SpriteScene1->SpriteList->Add(Sprite2);

    }

    void __fastcall TForm1::Run1Click(TObject *Sender)

    {

      Hermes1->Run();

    }

    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,

      TShiftState Shift)

    {

      if ((Shift.Contains(ssAlt)) && (Key == `X'))

        Close();

    }
Don't forget that when using this program, you will probably have to explicitly add `Creatures1.pas` to your project:
    //--------------------------------------------------------------------------

    #include <vcl\vcl.h>

    #pragma hdrstop

    //--------------------------------------------------------------------------

    USEFORM("Main.cpp", Form1);

    USERES("SpriteTest1.res");

    USEUNIT("..\..\Units\creatures1.pas");

    //--------------------------------------------------------------------------

    WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

    {

      try

      {

        Application->Initialize();

        Application->CreateForm(__classid(TForm1), &Form1);

        Application->Run();

      }

      catch (Exception &exception)

      {

        Application->ShowException(&exception);

      }

      return 0;

    }
Figure 28.3 shows the TestSpriteScene program in action. In this program, I make no attempt to animate the sprites. To do so, you can merely change the x- and y-coordinates at which they are shown and then wait for the screen to be updated automatically by the timer. Figure 28.3. The TestSpriteScene program shows a background bitmap with two sprites placed on it. To work with the `TSpriteScene` object, you should drop a `THermes`, a `TSpriteScene`, and one or more `TSprite` objects on a form. Connect the `THermes` object to the sprite scene. Pick a background bitmap and a transparent color for the sprite scene. In the sample program, I use `BackGrd2.bmp` for the background and `254` for the transparent color. For the bitmaps that ship in the `Chap28/Media` directory, I assume that the background color is the `254` element in the palette of the background bitmap. This color is a robin's egg blue, which means that this particular shade of blue cannot be used in any of the bitmaps except for areas that you want to be transparent. Don't forget that if a particular RGB pattern such as `255, 0, 255` is designated as the transparent color, you can change any one value to get a nearby color that is not going to appear as transparent. For example, you can choose `255, 1, 255`, which is virtually identical in shade to `255, 0, 255`, but it will not be transparent to the user and can be shown in your bitmaps. Connect the `TSprite` objects to a bitmap. I connect the first sprite to `Queen2.bmp` and the second sprite to `Monk2.bmp`. NOTE: These graphical figures were created by Kari Marcussen and, like all the artwork in this book, are copyrighted and cannot be used in your own programs. You can, of course, use the art in the "privacy of your own home," but you cannot distribute programs of any kind, even free programs, that contain this art. The `XPos` and `YPos` positions for the first sprite are 100x200, whereas the second sprite is 400x200. You don't have to be concerned with the size of the sprite itself, as the components will calculate the size automatically at runtime. You need to have some way of telling the `TSpriteScene` object about the sprites that it owns. I use a simple event handler of `TSpriteScene` called `OnSetupSurface` for this purpose:
    void __fastcall TForm1::SpriteScene1SetupSurfaces(TObject *Sender)

    {

      SpriteScene1->SpriteList->Add(Sprite1);

      SpriteScene1->SpriteList->Add(Sprite2);

    }
`SpriteList` is a simple `TList` descendant. Keeping all the child sprites in a list allows for easy deallocation of memory:
    void __fastcall TSpriteScene::DestroyObjects(void)

    {

      if(FSpriteList != NULL)

      {

        for(int i = 0; i < FSpriteList->Count; i++)

          ((TSprite*)(FSpriteList->Items[i]))->Surface->Release();

        FSpriteList->Clear();

      }

    }
This kind of routine is important because you might want to switch between numerous `TScene` descendants during the course of a program. In other words, your game may have more than one scene in it. I will explain this process in more depth later in the chapter. However, the quick overview is that you can simply write code that looks like this:
    Hermes1->Scene = Scene1;

    ... // Code omitted here.

    Hermes1->Scene = Scene2;
In this case, the program starts by showing `Scene1` and then at some later point switches to `Scene2`. When you switch scenes, the `DestroyObjects` method for the previous scene is called, thereby deallocating all the memory associated with background and sprite bitmaps. At the same time, new memory is allocated for the bitmaps used in `Scene2`. The `SpriteList` also is important when you switch away from a program in Exclusive mode and then press Alt+Tab to move back to it. At those times, the following routine is called:
    long __fastcall TSpriteScene::RestoreSurfaces(void)

    {

      TSprite *Sprite;

      HRESULT Result;

      Result = TScene::RestoreSurfaces();

      if(Result == DD_OK)

      {

            for(int i = 0; i < FSpriteList->Count; i++)

        {

          Sprite = (TSprite *)FSpriteList->Items[i];

          Result = Sprite->Surface->Restore();

          if(Result == DD_OK)

            DDReloadBitmapLib(FLib, Sprite->Surface, Sprite->Bitmap);

          else

            break; // Exit on error

        }
      }
This routine iterates through all the bitmaps in the `SpriteList` and restores each surface. This process occurs so quickly that the user simply sees the scene being restored all at once and is not aware that a list of objects is being re-created. You can now run the program, trying it first in windowed mode, where the output will appear a bit muddy because the transparent color probably won't work correctly. After you're sure everything is running correctly, you can try the program in Exclusive mode. When in Exclusive mode, you can press Alt+Tab to move away from the main game window and view some other program such as the Windows Explorer. You can then press Alt+Tab to go back to the game window. This capability is one of the key features of DirectX. If you're concerned about the `TransparentColor` not working right in windowed mode, you can try using a common color such as black for your `TransparentColor`. It might work correctly, or you can play with the Windows palette system to make sure the right palette is selected when your program appears in a window. For now, however, my graphics engine assumes that you want to run your program in Exclusive mode, and support for windowed mode is available only so that you can easily debug your programs. After all, DirectX doesn't really provide many advantages over GDI in windowed mode. The whole point of this process is to switch into Exclusive mode where you can get the following: High performance Control over the size of the palette Control over the dimensions of the screen When I say that DirectX offers high performance, I mean that in Exclusive mode you can write directly to the video buffer, thereby obtaining the same kind of performance you would expect from a DOS program. The actual degree of performance improvement you get is dependent on the amount of RAM on your video card and on the bus between RAM and your video card. The ideal situation is to have a video card with enough RAM in it to hold all the bitmaps used by any one scene. Therefore, 2MB of memory on your video card is pretty much a minimum, and a strong argument can be made in favor of having 4MB. The code that ships with this book will work fine, however, with a small amount of RAM such as 524KB. The actual system for drawing to the video buffer involves a process called flipping. When using this technology, the program draws the image you want to show the user to an offscreen buffer and then flips that offscreen buffer into the part of video memory the user sees. The user therefore never sees the relatively lengthy process of drawing to the buffer but sees only the finished picture when it is blitted, or "flipped," onto the screen. The core job of the graphics engine presented in this chapter is to automate this process so you don't have to think about it. ## THermesChart: Working with a Tiled World Some of the most interesting games consist of large worlds that fill up screen after screen with information. Classic examples of this kind of game are Heroes of Might and Magic, WarCraft, and Civilization. The `THermesChart` component shows how to work with one of these worlds. Tiled worlds are made up of bitmaps that consist of lots of tiny tiles that can be combined in various ways to create maps. A picture of one of the tiled bitmaps is shown in Figure 28.4, and a small portion of the world created from these tiles is shown in Figure 28.5. Figure 28.4. The tiles from which a tiled world is made. Figure 28.5. A tiled world created by the `THermesChart` component from the bitmaps shown in Figure 28.4. The declaration for the `THermesChart` component is shown in Listing 28.10, although the test program for it is shown in Listings 28.11 and 28.12. #### Listing 28.10. The declaration for the THermesChart class and its parent.
     struct TSpecialRect

    {

      bool IsCreature;

      RECT R1;

      RECT R2;

    };

    class THermesTiler : public TScene

    {

      typedef TScene inherited;

      friend THermesChart;

      private:

      int FMaxMapRows;

      int FMaxMapCols;

      int FBitmapWidth;

      int FBitmapHeight;

      System::AnsiString FTileMap;

      IDirectDrawSurface* FTileSurface;

    protected:

      __fastcall virtual ~THermesTiler(void);

      virtual void __fastcall DrawScene(void);

      virtual void __fastcall SetupSurfaces(System::TObject* Sender);

      virtual TSpecialRect __fastcall GetRect(int Col, int Row) = 0;

      virtual bool __fastcall MoveGrid(int Col, int Row, bool CallFlip) = 0;

    public:

      __fastcall virtual THermesTiler(Classes::TComponent* AOwner);

      virtual void __fastcall DestroyObjects(void);

      Windows::TRect __fastcall MapTypeToTileRect(int MapType);

      virtual long __fastcall RestoreSurfaces(void);

    __published:

      __property System::AnsiString TileMap =

        {read=FTileMap, write=FTileMap, nodefault};

      __property int BitmapWidth =

        {read=FBitmapWidth, write=FBitmapHeight, nodefault};

      __property int BitmapHeight =

        {read=FBitmapHeight, write=FBitmapHeight, nodefault};

    };

    typedef void __fastcall (__closure *TMoveHeroEvent)(System::TObject* Sender,

      	const tagPOINT &NewPos, int NewType, bool &MoveOk);

    class THermesChart : public THermesTiler

    {

      typedef THermesTiler inherited;

    private:

      Creatures1::TCreature* FHero;

      bool FHeroActive;

      TMoveHeroEvent FOnHeroMove;

      bool __fastcall CheckHeroPos(Creatures1::TCreatureList* HeroList,

        int Col, int Row);

      Windows::TRect __fastcall MapTypeToCreature(int Col, int Row);

      virtual TSpecialRect __fastcall GetRect(int Col, int Row);

      virtual bool __fastcall MoveGrid(int Col, int Row, bool CallFlip);

      protected:

      void __fastcall MoveHero(int NewCol, int NewRow);

      virtual void __fastcall SetupSurfaces(System::TObject* Sender);

    public:

      __fastcall virtual ~THermesChart(void);

      void __fastcall Move(int Value);

      __published:

      __property bool HeroActive = {read=FHeroActive, write=FHeroActive, nodefault};

      __property TMoveHeroEvent OnHeroMove = {read=FOnHeroMove, write=FOnHeroMove};

    public:

      __fastcall virtual THermesChart(Classes::TComponent* AOwner)

        : Mercury2::THermesTiler(AOwner) { }
`};` #### Listing 28.11. The header file for the TilerTest program.
    ///////////////////////////////////////

    // Main.h

    // Project: TilerTest1

    // Copyright (c) 1997 by Charlie Calvert

    //

    #ifndef MainH

    #define MainH

    #include <vcl\Classes.hpp>

    #include <vcl\Controls.hpp>

    #include <vcl\StdCtrls.hpp>

    #include <vcl\Forms.hpp>

    #include "Creatures1.hpp"

    #include <vcl\Menus.hpp>

    #include "Mercury2.h"

    class TForm1 : public TForm

    {

    __published:

      THermes *Hermes1;

      THermesChart *HermesChart1;

      TFileCreatureList *FileCreatureList1;

      TMainMenu *MainMenu1;

      TMenuItem *Run1;

      void __fastcall Run1Click(TObject *Sender);

      void __fastcall FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift);

    private:

    public:

      __fastcall TForm1(TComponent* Owner);

    };

    extern TForm1 *Form1;
`#endif` #### Listing 28.12. The main source for the TestTiler program.
    ///////////////////////////////////////

    // Main.cpp

    // Project: TilerTest1

    // Copyright (c) 1997 by Charlie Calvert

    //

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Main.h"

    #pragma link "Creatures1"

    #pragma link "Mercury2"

    #pragma resource "*.dfm"

    TForm1 *Form1;

    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

    }

    void __fastcall TForm1::Run1Click(TObject *Sender)

    {

      Hermes1->Run();

    }

    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,

      	TShiftState Shift)

    {

      if (Shift.Contains(ssAlt) && (Key == `X'))

        Close();

      else

        HermesChart1->Move(Key);
`}` By now, you should be getting used to the fact that these components are very easy to use. The only custom code you have to write is to define how to exit the program:
    if (Shift.Contains(ssAlt) && (Key == `X'))

      Close();
Other than this one simple statement, all the other "coding" involves nothing more than changing a few properties. This particular example uses bitmaps that are stored in a DLL called `BitDll.dll`. To create a DLL of this type, simply build a resource file containing the bitmaps you want to use and then add the RC file to a DLL project. You can create a DLL by choosing File | New | DLL from the BCB menu. Here is the RC code for a sample resource that contains multiple bitmaps:
    Back BITMAP "PANEL4.BMP"

    TileMap BITMAP "TILEMAP.BMP"

    City BITMAP "FLOOR1.BMP"

    Dirs BITMAP "COMPDIRS.BMP"

    Treasure BITMAP "TREASURE.BMP"

    Sage1 BITMAP "SAGE1.BMP"
You can access the various bitmaps in the DLL by name. For example, you should set the `BackgroundBitmap` property of the `THermesChart` to `Back` and the `TileMap` property to `TileMap`. As long as the `DLLName` property is pointing to a valid DLL, you need do nothing else. All `TScene` descendants know how to read bitmaps from either a file on disk or from a DLL. You'll discover several obvious advantages to using a DLL rather than a raw BMP file: You can ship one file with your game rather than a series of files. The DLL helps to conceal your bitmaps from prying eyes. Other developers will know how to get at them, but most users won't be able to find them. You can ship a series of DLLs with your projects and then load and unload the DLLs as needed to access various different bitmaps. If you switch scenes during the run of a program, the DLL associated with one scene will be unloaded from memory, and the DLL associated with the new scene will be loaded. The tiled world that you create depends on two interrelated binary files. One binary file contains the screen map that you create, and the second contains a list of creatures that inhabit the map. The screen map is simply an array 255x252 characters wide. At this time, the map you make must be this size, though I will provide alternative sizes at a later date. Check my Web site for updates. Each element in the bitmap containing the tiles has a number associated with it. These numbers are described in the following enumerated type:
    enum TMapType {mtGrass, mtWater, mtMountain, mtRoad, mtWater2,

                   mtFootHill, mtNorthShore, mtWestShore, SouthShore, mtEastShore,

                   mtSWShore, mtSEShore, mtNWShore, mtNEShore,

                   mtWNWShore, mtWSEShore, mtESEShore, mtENEShore,

                   mtBlank1, mtBlank2, mtBlank3, mtBlank4, mtAllSnow,

                   mtSnowyMountain, mtSouthMtn, mtWestMtn, mtNorthMtn, mtEastMtn,

                   mtSEMtn, mtSWMtn, mtNWMtn, mtNEMtn, mtNWFootHill,

                   mtNEFootHill, mtSEFootHill, mtSWFootHill,

                   mtNorthFootHill, mtEastFootHill, mtSouthFootHill,

                   mtWestFootHill, mtNEDiagShore, mtSEDiagShore,

                   mtSWDiagShore, mtNWDiagShore, mtSWBendShore, mtSEBendShore,

                   mtNWBendShore, mtNEBendShore, mtENBendShore, mtWNBendShore,

                   mtWSBendShore, mtESBendShore, mtCity, mtCreature};
`mtGrass` is element `0` in this enumerated type. `mtWater` is element `1`; `mtMountain`, element `2`; and so on. This type is not used in the TestTiler1 program, but it will come in handy later in the chapter. Here is a simple two-dimensional array that encodes a small world:
    1 1 1 1 1

    1 1 0 1 1

    1 0 2 0 1

    1 1 0 1 1

    1 1 1 1 1
This world consists of a small island with a mountain in the center of it. The entire island is surrounded by water. Of course, this world is tiny and cannot be used by the `THermesChart` component. To find a world that can be used by `THermesChart`, look in the `Media` directory on the CD that accompanies this book. There you will find a textual representation of a world in a file called `Screen.txt` and binary translation of that file called `Screen.dta`. A simple program called TextToBinary found on the CD will translate the textual representation of a world to a binary file. The code that does so takes only a few lines:
    __fastcall TForm1::TForm1(TComponent* Owner)

      : TForm(Owner)

    {

      FMapPointList = new TMapPointList();

    }

    void __fastcall TForm1::FormDestroy(TObject *Sender)

    {

      FMapPointList->Free();

    }

    void __fastcall TForm1::Button1Click(TObject *Sender)

    {

      int X, Y;

      if (OpenDialog1->Execute())

      {

        FMapPointList->ReadBinary(OpenDialog1->FileName, X, Y);

        AnsiString S = ChangeFileExt(OpenDialog1->FileName, ".txt");

        FMapPointList->WriteText(S, X, Y);

      }

    }

    void __fastcall TForm1::Button2Click(TObject *Sender)

    {

      if (OpenDialog1->Execute())

      {

        FMapPointList->ReadText(OpenDialog1->FileName);

        AnsiString S = ChangeFileExt(OpenDialog1->FileName, ".dta");

        FMapPointList->WriteBinary(S,

          FMapPointList->StartX, FMapPointList->StartY);

      }

    }
> > The constructor and destructor for this program create an object of type `TMapPointList`. You can find the `TMapPointList` object in a file called `Creatures1.pas` on the CD. To translate a binary file to text, simply call the methods of `TMapPointList` called `ReadBinary` and `WriteText`. To reverse the process, call `ReadText` and `WriteBinary`. NOTE: I don't really expect you to create a world by editing a text file. The file is not ready at the time of this writing, but the CD that ships with this book may contain a program in the `Chap28` directory that will allow you to edit a map from inside Windows by using the mouse. Check my Web site for updates to this program. In particular, note that there are methods in `Creatures1.pas` that make it easy to create this type of program. For instance, note the following methods of `TCreatureList`:
> >     function NameFromLocation(ACol, ARow: Integer): string;

> >     function TypeFromLocation(ACol, ARow: Integer): string;

> >     function CreatureFromLocation(ACol: Integer; ARow: Integer): TCreature;
Methods like this allow you to quickly identify the tile at a particular location. You can then use the `Map` array property from the same object to change these tiles. Besides the screen file, you also need to maintain a file that tracks the creatures shown on your tiled world. In the sample programs that ship with this book, this file is called `Creatures.dta`. You can edit `Creatures.dta` with a simple program called Entity. Most of this program is written in Pascal, so I won't discuss its code here. However, it compiles fine in C++Builder. NOTE: I apologize for including so much Pascal code in this chapter. For various reasons, I need to have these objects working in both C++Builder and Delphi, and the simplest way to do that is to write the core objects and utilities in Object Pascal. In this particular case, I have taken a Delphi program and swapped out the Pascal version of the project source and substituted a version written in BCB. That way, I could get the program to compile in C++Builder by merely changing a few lines of code, all of which are generated automatically by the IDE. In short, I started a new project, removed the main form, and added the main form from the Pascal version of the Entities program. That was all I had to do to translate the program from Delphi to C++Builder. The Entity program tracks the creature and screen file that it edits through the Registry. To do so, it uses the same technique I discussed in Chapter 13, "Flat-File, Real-World Databases." In particular, the following code should jog your memory as to how I retrieve the current creature and screen file from the Registry:
    RegFile := TRegIniFile.Create(`Software\Charlie''s Stuff\Entities');

    FCreatureFile := RegFile.ReadString(`FileNames', `CreatureFile', `');

    FScreenFile := RegFile.ReadString(`FileNames', `ScreenFile', `');
Running the program once places the proper entries in the Registry. You can then edit the Registry to enter in the names of the actual files you want to use. In particular, you should use the Windows utility called `RegEdit.exe` to edit the area under `HKEY_CURRENT_USER\Charlie's Stuff\Entities`. The creatures file contains a number of creature profiles, as shown in Figure 28.6 and Figure 28.7. Figure 28.6. The hero's profile as shown in the Entity program. Figure 28.7. A hostile creature's profile as shown in the Entity program. When you first open the screen, be sure that the hero is located at column `11` and row `11`. To set this up properly, you need to edit the `Col` and `Row` fields, as well as the `Scrn Col`, `Scrn Row`, `Map Col`, and `Map Row`. You need all these values because you can never see the whole map at one time. Instead, you see a window into the entire grid. You therefore need to track where the window is on the grid, where the character is on the grid, and where the character is on the window currently opened onto the grid. You have three pairs of values, all of which are tracked for you automatically by the `THermesChart` object. Now that you've examined the screen and creature files, you're ready to run the `TestTiler` program. Go ahead and run it once in windowed mode to make sure that you're reaching all the files in question. Then run the program again in Exclusive mode. NOTE: You can possibly mangle the screen file by exiting the TilerTest1 program incorrectly. In other words, if TilerTest1 crashes in mid-run and you reboot, you can lose the contents of the screen file. To check whether you've lost the contents, check that your screen file is equal to 64,276 bytes. If it is some other value, the file is probably corrupt and should be refreshed from the file on the CD that accompanies the book. If you have this problem, you will probably get a `Range` error when trying to run the program. The only point left for you to learn is how to move the character on the screen and how to scroll around through the world. To do so, simply modify the `OnKeyDown` event for `TilerTest1`:
    void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,

                                       TShiftState Shift)

    {

      if (Shift.Contains(ssAlt) && (Key == `X'))

        Close();

      else

        HermesChart1->Move(Key);

    }
This code will exit the program if the user presses Alt+X; otherwise, it will pass the current keystroke on the `HermesChart` object. This object responds to presses on the arrow keys by moving either the main character or by scrolling the entire map. Press the Insert key to switch between these two modes. Figure 28.8 shows the main character walking on the map. Figure 28.8.The main character exploring the shore of a lake, with mountains nearby. ## The Mercury2 Source Code The source for `Mercury2.cpp` is shown in Listings 28.13 and 28.14. Please note that several stand-alone functions right at the top of `Mercury2.cpp` are only slightly modified versions of code that appears in the `DDUtils.cpp` file that ships with the DirectDraw SDK. I could not let you use the original functions, available on the Microsoft Web site, because they do not take into account the possibility that the user might want to store bitmaps in a DLL. #### Listing 28.13. The header for the Mercury2 unit.
    #ifndef Mercury2H

    #define Mercury2H

    #include <Creatures1.hpp>

    #include <ddraw.h>

    #include <DsgnIntf.hpp>

    #include <OLE2.hpp>

    #include <Graphics.hpp>

    #include <Forms.hpp>

    #include <Controls.hpp>

    #include <Classes.hpp>

    #include <Windows.hpp>

    #include <System.hpp>

    namespace Mercury2

    {

    //-- type declarations ------------------------------------------------------

    typedef tagPALETTEENTRY T256PalEntry[256];

    typedef tagRGBQUAD TRGB[256];

    typedef TRGB *PRGB;

    class THermes;

    class TScene;

    class THermesTiler;

    class THermesChart;

    class TSpriteScene;

    class TDraw;

    class TDraw : public Classes::TComponent

    {

      typedef Classes::TComponent inherited;

      friend THermesTiler;

      friend THermesChart;

      friend TScene;

      friend TSpriteScene;

    private:

      AnsiString FDLLName;

      THermes* FHermes;

      HANDLE FLib;

      IDirectDrawPalette* FPalette;

      int FTransparentColor;

      bool __fastcall CreateDDSurface(IDirectDrawSurface* &DDS,

        System::AnsiString BitmapName, bool UsePalette);

      IDirectDrawSurface* __fastcall CreateSurface(HANDLE Bitmap);

      HANDLE __fastcall GetDib(HANDLE Instance,  System::AnsiString S);

      IDirectDrawPalette* __fastcall LoadPalette(HANDLE Instance,

        const System::AnsiString BitmapName);

    public:

      __fastcall virtual TDraw(Classes::TComponent* AOwner);

      __fastcall virtual ~TDraw(void);

      void __fastcall WriteXY(int X, int Y,  System::AnsiString S);

      __property int TransparentColor =

        {read=FTransparentColor, write=FTransparentColor, nodefault};

    __published:

      __property AnsiString DLLName =

        {read=FDLLName, write=FDLLName};

    };

    class TScene : public TDraw

    {

      typedef TDraw inherited;

      friend THermes;

    private:

      System::AnsiString FBackgroundBitmap;

      Graphics::TColor FBackColor;

      RECT FBackRect;

      tagPOINT FBackOrigin;

      bool FBlankScene;

      bool FShowBitmap;

      IDirectDrawSurface* FWorkSurface;

      Classes::TNotifyEvent FOnSetupSurfaces;

      Classes::TNotifyEvent FOnDrawScene;

    public:

      __fastcall virtual TScene(Classes::TComponent* AOwner);

      virtual void __fastcall DestroyObjects(void);

      virtual void __fastcall DrawScene(void);

      virtual long __fastcall RestoreSurfaces(void);

      virtual void __fastcall SetupSurfaces(System::TObject* Sender);

    __published:

      __property System::AnsiString BackgroundBitmap =

        {read=FBackgroundBitmap, write=FBackgroundBitmap,	nodefault};

      __property bool BlankScene = {read=FBlankScene, write=FBlankScene, nodefault};

      __property long OriginX ={read=FBackOrigin.x, write=FBackOrigin.x, nodefault};

      __property long OriginY ={read=FBackOrigin.y, write=FBackOrigin.y, nodefault};

      __property bool ShowBitmap = {read=FShowBitmap, write=FShowBitmap, default=1};

      __property Classes::TNotifyEvent OnDrawScene =

        {read=FOnDrawScene, write=FOnDrawScene};

      __property Classes::TNotifyEvent OnSetupSurfaces =

        {read=FOnSetupSurfaces, write=FOnSetupSurfaces};

      __property Graphics::TColor BackColor =

        {read=FBackColor, write=FBackColor, nodefault};

      __property TransparentColor ;

    public:

      __fastcall virtual ~TScene(void) { }

    };

    class THermes : public Classes::TComponent

    {

      typedef Classes::TComponent inherited;

      friend THermesChart;

      friend TDraw;

    private:

      bool FActive;

      Creatures1::TFileCreatureList* FCreatureList;

      HWND FHandle;

      bool FTimerOdd;

      int FTimerInterval;

      bool FExclusive;

      Classes::TNotifyEvent FPaintProc;

      TScene* FScene;

      bool FUseTimer;

      bool FFirstTime;

      IDirectDraw* FDirectDraw;

      IDirectDrawSurface* FBackSurface;

      IDirectDrawClipper* FClipper;

      IDirectDrawSurface* FPrimarySurface;

      bool __fastcall CreatePrimary(void);

      void __fastcall DDTest(long hr,  System::AnsiString S);

      void __fastcall InitBaseObjects(void);

      bool __fastcall MakeItSo(long DDResult);

      void __fastcall SetScene(TScene* Scene);

      bool __fastcall SetUpBack(void);

      protected:

      void __fastcall DrawBitmaps(void);

      virtual long __fastcall RestoreSurfaces(void);

    public:

      __fastcall virtual THermes(Classes::TComponent* AOwner);

      __fastcall virtual ~THermes(void) {}

      void __fastcall EndExclusive(void);

      void __fastcall ErrorEvent( System::AnsiString S);

      void __fastcall Flip(void);

      virtual void __stdcall InitObjects(void);

      virtual void __fastcall Run(void);

      __property bool Active = {read=FActive, write=FActive, nodefault};

      __property IDirectDrawSurface* BackSurface =

       {read=FBackSurface, write=FBackSurface, nodefault};

      __property Classes::TNotifyEvent OnDrawBitmap =

       {read=FPaintProc, write=FPaintProc};

    __published:

      __property Creatures1::TFileCreatureList* CreatureList =

        {read=FCreatureList, write=FCreatureList,    nodefault};

      __property bool Exclusive = {read=FExclusive, write=FExclusive, nodefault};

      __property TScene* Scene = {read=FScene, write=SetScene, nodefault};

      __property int TimerInterval =

        {read=FTimerInterval, write=FTimerInterval, nodefault};

      __property bool UseTimer = {read=FUseTimer, write=FUseTimer, nodefault};

    };

    struct TSpecialRect

    {

      bool IsCreature;

      RECT R1;

      RECT R2;

    };

    class THermesTiler : public TScene

    {

      typedef TScene inherited;

      friend THermesChart;

      private:

      int FMaxMapRows;

      int FMaxMapCols;

      int FBitmapWidth;

      int FBitmapHeight;

      System::AnsiString FTileMap;

      IDirectDrawSurface* FTileSurface;

    protected:

      __fastcall virtual ~THermesTiler(void);

      virtual void __fastcall DrawScene(void);

      virtual void __fastcall SetupSurfaces(System::TObject* Sender);

      virtual TSpecialRect __fastcall GetRect(int Col, int Row) = 0;

      virtual bool __fastcall MoveGrid(int Col, int Row, bool CallFlip) = 0;

    public:

      __fastcall virtual THermesTiler(Classes::TComponent* AOwner);

      virtual void __fastcall DestroyObjects(void);

      Windows::TRect __fastcall MapTypeToTileRect(int MapType);

      virtual long __fastcall RestoreSurfaces(void);

    __published:

      __property System::AnsiString TileMap =

        {read=FTileMap, write=FTileMap, nodefault};

      __property int BitmapWidth =

        {read=FBitmapWidth, write=FBitmapHeight, nodefault};

      __property int BitmapHeight =

        {read=FBitmapHeight, write=FBitmapHeight, nodefault};

    };

    typedef void __fastcall (__closure *TMoveHeroEvent)(System::TObject* Sender,

      const tagPOINT &NewPos, int NewType, bool &MoveOk);

    class THermesChart : public THermesTiler

    {

      typedef THermesTiler inherited;

    private:

      Creatures1::TCreature* FHero;

      bool FHeroActive;

      TMoveHeroEvent FOnHeroMove;

      bool __fastcall CheckHeroPos(Creatures1::TCreatureList* HeroList,

        int Col, int Row);

      Windows::TRect __fastcall MapTypeToCreature(int Col, int Row);

      virtual TSpecialRect __fastcall GetRect(int Col, int Row);

      virtual bool __fastcall MoveGrid(int Col, int Row, bool CallFlip);

      protected:

      void __fastcall MoveHero(int NewCol, int NewRow);

      virtual void __fastcall SetupSurfaces(System::TObject* Sender);

    public:

      __fastcall virtual ~THermesChart(void);

      void __fastcall Move(int Value);

      __published:

      __property bool HeroActive = {read=FHeroActive, write=FHeroActive, nodefault};

      __property TMoveHeroEvent OnHeroMove = {read=FOnHeroMove, write=FOnHeroMove};

    public:

      __fastcall virtual THermesChart(Classes::TComponent* AOwner)

        : Mercury2::THermesTiler(AOwner) { }

    };

    class TSprite;

    class TSprite : public Classes::TComponent

    {

      typedef Classes::TComponent inherited;

      friend TSpriteScene;

    private:

      System::AnsiString FBitmap;

      tagPOINT FPosition;

      IDirectDrawSurface* FSurface;

      RECT FRect;

    public:

      bool __fastcall IsHit(int X, int Y);

      __property IDirectDrawSurface* Surface=

        {read=FSurface, write=FSurface, nodefault};

      __property RECT Rect = {read=FRect, write=FRect};

      __published:

      __property System::AnsiString Bitmap={read=FBitmap, write=FBitmap, nodefault};

      __property long XPos = {read=FPosition.x, write=FPosition.x, nodefault};

      __property long YPos = {read=FPosition.y, write=FPosition.y, nodefault};

    public:

      __fastcall virtual TSprite(Classes::TComponent* AOwner)

        : Classes::TComponent(AOwner) { }

      __fastcall virtual ~TSprite(void) { }

    };

    class TSpriteScene : public TScene

    {

      typedef TScene inherited;

    private:

      Classes::TList* FSpriteList;

    protected:

      __fastcall virtual ~TSpriteScene(void);

      virtual void __fastcall SetupSurfaces(System::TObject* Sender);

    public:

      __fastcall virtual TSpriteScene(Classes::TComponent* AOwner);

      virtual void __fastcall DestroyObjects(void);

      void __fastcall AddSprite(TSprite* Sprite);

      virtual void __fastcall DrawScene(void);

      virtual long __fastcall RestoreSurfaces(void);

      __property Classes::TList* SpriteList = {read=FSpriteList, write=FSpriteList,nodefault};

    __published:

      __property TransparentColor;

    };

    ////////////////////////////////////////

    // TSceneEditor ////////////////////////

    ////////////////////////////////////////

    class TSceneEditor: public TComponentEditor

    {

    protected:

      virtual __fastcall void Edit(void);

    public:

      virtual __fastcall TSceneEditor(TComponent *AOwner, TFormDesigner *Designer)

        : TComponentEditor(AOwner, Designer) {}

    };

    //-- var, const, procedure --------------------------------------------------

    #define Timer1 (Byte)(1)

    extern void __fastcall Register(void);

    }/* namespace Mercury1 */

    #if !defined(NO_IMPLICIT_NAMESPACE_USE)

    using namespace Mercury2;

    #endif

    //-- end unit ----------------------------------------------------------------
`#endif // Mercury2` #### Listing 28.14. The main source file for the Mercury2 module.
    ///////////////////////////////////////

    // Mercury2.cpp

    // DirectX Graphics

    // Copyright (c) 1997 by Charlie Calvert

    // Thanks to John Thomas, Stuart Fullmar and Jeff Cottingham

    ///////////////////////////////////////

    #include <vcl\vcl.h>

    #pragma hdrstop

    #include "Mercury2.h"

    #include "errors1.h"

    #include "SceneEditor1.h"

    #pragma link "Errors1.obj"

    #pragma link "SceneEditor1.obj"

    /*{ ------------------------ }

      { --- THermes ------------ }

      { ------------------------ } */

    THermes *AHermes;

    void Timer2Timer(HWND H, UINT Msg, UINT Event, DWORD Time)

    {

      if (AHermes->Active)

        AHermes->Flip();

    }

    namespace Mercury2

    {

      // Slightly modified version of code from DDUtils.cpp

      HRESULT DDCopyBitmap(IDirectDrawSurface *pdds,

        HBITMAP hbm, int x, int y, int dx, int dy)

      {

        HDC                 hdcImage;

        HDC                 hdc;

        BITMAP              bm;

        DDSURFACEDESC      ddsd;

        HRESULT             hr;

        if (hbm == NULL || pdds == NULL)

            return E_FAIL;

        //

        // make sure this surface is restored.

        //

        pdds->Restore();

        //

        //  select bitmap into a memoryDC so we can use it.

        //

        hdcImage = CreateCompatibleDC(NULL);

        if (!hdcImage)

            OutputDebugString("createcompatible dc failed\n");

        SelectObject(hdcImage, hbm);

        //

        // get size of the bitmap

        //

        GetObject(hbm, sizeof(bm), &bm);    // get size of bitmap

        dx = dx == 0 ? bm.bmWidth  : dx;    // use the passed size, unless zero

        dy = dy == 0 ? bm.bmHeight : dy;

        //

        // get size of surface.

        //

        ddsd.dwSize = sizeof(ddsd);

        ddsd.dwFlags = DDSD_HEIGHT | DDSD_WIDTH;

        pdds->GetSurfaceDesc(&ddsd);

        if ((hr = pdds->GetDC(&hdc)) == DD_OK)

        {

            StretchBlt(hdc, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcImage, x, y, dx, dy, SRCCOPY);

            pdds->ReleaseDC(hdc);

        }

        DeleteDC(hdcImage);

        return hr;

      }

      // Slightly modified version of code from DDUtils.cpp

      void DDReloadBitmapLib(HANDLE Lib, IDirectDrawSurface *Surface,

        const AnsiString BitmapName)

      {

        HBITMAP Bitmap;

        if (Lib)

          Bitmap = LoadImage(Lib, BitmapName.c_str(),

            IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);

        else

          Bitmap = LoadImage(GetModuleHandle(NULL), BitmapName.c_str(),

            IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);

        if (!Bitmap)

          Bitmap = LoadImage(0, BitmapName.c_str(), IMAGE_BITMAP,

            0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);

        if (!Bitmap)

          throw Exception("Unable to load bitmap %s",

            OPENARRAY(TVarRec, (BitmapName)));

        DDCopyBitmap(Surface, Bitmap, 0, 0, 0, 0);

        DeleteObject(Bitmap);

      }

      // Slightly modified version of code from DDUtils.cpp

      IDirectDrawSurface *DDCreateSurface(IDirectDraw *DD, DWORD Width, DWORD Height,

        	bool SysMem, bool Trans, DWORD dwColorKey)

      {

        DDSURFACEDESC SurfaceDesc;

        HRESULT hr;

        DDCOLORKEY ColorKey;

        IDirectDrawSurface *Surface;

        // fill in surface desc

        memset(&SurfaceDesc, 0, sizeof(DDSURFACEDESC));

        SurfaceDesc.dwSize = sizeof(SurfaceDesc);

        SurfaceDesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;

        SurfaceDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

        SurfaceDesc.dwHeight = Height;

        SurfaceDesc.dwWidth = Width;

        hr = DD->CreateSurface(&SurfaceDesc, &Surface, NULL);

        // set the color key for this bitmap

        if (hr == DD_OK)

        {

          if (Trans)

          {

            ColorKey.dwColorSpaceLowValue = dwColorKey;

            ColorKey.dwColorSpaceHighValue = dwColorKey;

            Surface->SetColorKey(DDCKEY_SRCBLT, &ColorKey);

          }

        }

        else

          throw EDDError("CreateSurface Failed in DDCreateSurface");

        return Surface;

      } // DDCreateSurface

      IDirectDrawClipper *CreateClipper(IDirectDraw *DD, HWND Handle)

      {

        HRESULT hr;

        IDirectDrawClipper *lpClipper;

        hr = DD->CreateClipper(0, &lpClipper, NULL);

        if (hr != DD_OK)

        {

          throw EDDError("No Clipper");

        }

        hr = lpClipper->SetHWnd(0, Handle);

        if (hr != DD_OK)

        {

          throw EDDError("Can''t set clipper window handle");

        }

        return lpClipper;

      }

    }

    __fastcall THermes::THermes(TComponent* AOwner)

    :TComponent(AOwner)

    {

      FHandle = ((TWinControl *)(AOwner))->Handle;

      FFirstTime = true;

    }// THermes

    void __fastcall THermes::EndExclusive()

    {

      FDirectDraw->FlipToGDISurface();

      if (FExclusive)

        FDirectDraw->SetCooperativeLevel(FHandle, DDSCL_NORMAL);

    }// EndExclusive

    void __fastcall THermes::ErrorEvent(String S)

    {

      FActive = false;

      EndExclusive();

      throw EDDError(S);

    }// ErrorEvent

    void __fastcall THermes::Run()

    {

      InitObjects();

      Flip();

    }// Run

    void __fastcall THermes::DDTest(long hr,  System::AnsiString S)

    {

      if (!Windows::Succeeded(hr))

        throw EDDError("DDTest Error: %s $%x %s",

          OPENARRAY(TVarRec, (S, int(hr), AnsiString(GetOleError(hr)))));

    }// DDTest

    /////////////////////////////////////////////////

    // Create the primary surface

    /////////////////////////////////////////////////

    bool __fastcall THermes::CreatePrimary(void)

    {

      DDSURFACEDESC SurfaceDesc;

      HResult hr;

      bool Result = true;

      memset(&SurfaceDesc, 0, sizeof(SurfaceDesc));

      SurfaceDesc.dwSize = sizeof(SurfaceDesc);

      if (!FExclusive){

        SurfaceDesc.dwFlags = DDSD_CAPS;

        SurfaceDesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

      }

      else{

        SurfaceDesc.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;

        SurfaceDesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |

                                      DDSCAPS_FLIP |

                                      DDSCAPS_COMPLEX;

        SurfaceDesc.dwBackBufferCount = 1;

      };

      hr = FDirectDraw->CreateSurface(&SurfaceDesc, &FPrimarySurface, NULL);

      if (hr != DD_OK)

        throw EDDError("THermes.CreatePrimary: %d %s",

          OPENARRAY(TVarRec, (int(hr), GetOleError(hr))));

      else

        return Result;

    }// CreatePrimary

    void __fastcall THermes::SetScene(TScene *Scene)

    {

      if (Scene)

        Scene->DestroyObjects();

      FScene = Scene;

    }// SetScene

    bool __fastcall THermes::SetUpBack(void)

    {

      bool Result = false;

      HResult hr;

      DDSCAPS DDSCaps;

      if (!FExclusive)

      {

        FBackSurface =

          Mercury2::DDCreateSurface(FDirectDraw, 640, 480, false, false, 0);

        if (FBackSurface == NULL)

          throw EDDError("Can''t set up back surface");

        FClipper = Mercury2::CreateClipper(FDirectDraw, FHandle);

        hr = FPrimarySurface->SetClipper(FClipper);

        if( hr != DD_OK )

          throw EDDError("Can''t attach clipper to front buffer");

      }

      else

      {

        memset(&DDSCaps, 0, sizeof(DDSCaps));

        DDSCaps.dwCaps = DDSCAPS_BACKBUFFER;

          hr = FPrimarySurface->GetAttachedSurface(&DDSCaps, &FBackSurface);

        if (hr != DD_OK)

          throw EDDError("TSpeedDraw.SetUpBack: %d %s",

            OPENARRAY(TVarRec, (int(hr), GetOleError(hr))));

        else

          Result = true;

      };

      return Result;

    }// SetUpBack

    void __fastcall  THermes::InitBaseObjects()

    {

      DWORD Flags;

      HResult hr;

      DDTest(DirectDrawCreate(NULL, &FDirectDraw, NULL), "InitObjects1");

      if (!FExclusive)

        Flags = DDSCL_NORMAL;

      else

        Flags = DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN;

      DDTest(FDirectDraw->SetCooperativeLevel(FHandle, Flags),"SetCooperativeLevel");

      if (FExclusive)

      {

        hr = FDirectDraw->SetDisplayMode(640, 480, 8);

        if(hr != DD_OK)

          throw EDDError("TSpeedDraw.InitObjects: %d %s",

            OPENARRAY(TVarRec, (int(hr), GetOleError(hr))));

      };

      CreatePrimary();

      SetUpBack();

      if (FCreatureList != NULL) FCreatureList->ReadFiles();

    }// InitBaseObjects

    /* Here are the steps in the initialization:

        Create DirectDraw Object

        SetCooperativeLevel

        if Exclusive then SetDisplayMode

        CreatePrimary

        SetupBack

        Create the work surface

        Set Active to true */

    void __stdcall THermes::InitObjects(void)

    {

      AHermes = this;

      if (FFirstTime)

      {

        InitBaseObjects();

        FFirstTime = false;

      };

      if ((Scene) && (Scene->FBackgroundBitmap != ""))

        Scene->SetupSurfaces(this);

      if (FUseTimer)

        SetTimer(FHandle, Timer1, FTimerInterval, (FARPROC)Timer2Timer);

      FActive = true;

    }// InitObjects

    void __fastcall THermes::DrawBitmaps()

    {

      if (Scene) Scene->DrawScene();

    }

    bool __fastcall THermes::MakeItSo(HResult DDResult)

    {

      bool Result;

      switch(DDResult)

      {

        case DD_OK:

          Result = true;

          break;

        case DDERR_SURFACELOST:

          Result = (RestoreSurfaces() == DD_OK);

          break;

        default:

          Result = DDResult != DDERR_WASSTILLDRAWING;

          break;

      };

      return Result;

    } // MakeItSo

    long __fastcall THermes::RestoreSurfaces()

    {

      HRESULT Result;

      Result = FPrimarySurface->Restore();

      if ((Result == DD_OK) && (Scene))

        Result = Scene->RestoreSurfaces();

      return Result;

    }// RestoreSurfaces

    void __fastcall THermes::Flip(void)

    {

      RECT R1, R;

      FTimerOdd = !FTimerOdd;

      if (!FActive)

        return;

      if (!FExclusive)

      {

        try

        {

          DrawBitmaps();

          GetWindowRect(FHandle, &R);

          R1 = Rect(0, 0, 640, 480);

          DDTest(FPrimarySurface->Blt(&R, FBackSurface, &R1, 0, NULL), "Flip");

        }

        catch(Exception &E)

        {

          ErrorEvent("Flipping");

        };

      }

      else

      {

        try

        {

          if (FActive)

          {

            do

            {

               Application->ProcessMessages();

            } while(!MakeItSo(FPrimarySurface->Flip(NULL, DDFLIP_WAIT)));

            DrawBitmaps();

          }

        }

        catch(...)

        {

          ErrorEvent("Flipping");

        }

      }

    }// Flip

    ///////////////////////////////////////

    // TDraw //////////////////////////////

    ///////////////////////////////////////

    __fastcall TDraw::TDraw(Classes::TComponent* AOwner)

      : TComponent(AOwner)

    {

    }

    __fastcall TDraw::~TDraw(void)

    {

      if (FLib != NULL)

        FreeLibrary(FLib);

    }

    bool __fastcall TDraw::CreateDDSurface(IDirectDrawSurface* &DDS,

      System::AnsiString BitmapName, bool UsePalette)

    {

      bool Result;

      DDCOLORKEY ColorKey;

      HRESULT hr;

      HANDLE Dib;

      if (UsePalette)

      {

        FPalette = LoadPalette(FLib, BitmapName.c_str());

        if (!FPalette)

          throw EDDError("LoadPalette Failed");

        FHermes->FPrimarySurface->SetPalette(FPalette);

      }

      Dib = GetDib(FLib, BitmapName);

      DDS = CreateSurface(Dib);

      if (!DDS)

        throw EDDError("CreateSurface Failed");

      ColorKey.dwColorSpaceLowValue = FTransparentColor;

      ColorKey.dwColorSpaceHighValue = FTransparentColor;

      hr = DDS->SetColorKey(DDCKEY_SRCBLT, &ColorKey);

      if (hr != DD_OK)

        throw EDDError("TSpeedDraw.CreateDDSurface: %d %s",

          OPENARRAY(TVarRec, (int(hr), GetOleError(hr))));

      else

        Result = True;

      return Result;

    }

    IDirectDrawSurface* __fastcall TDraw::CreateSurface(HANDLE Bitmap)

    {

      DDSURFACEDESC SurfaceDesc;

      Windows::TBitmap BM;

      IDirectDrawSurface *Result;

      if (Bitmap == 0)

        throw Exception("No Bitmap in CreateSurface");

      try

      {

        GetObject(Bitmap, sizeof(BM), &BM);

        memset(&SurfaceDesc, 0, sizeof(SurfaceDesc));

        SurfaceDesc.dwSize = sizeof(SurfaceDesc);

        SurfaceDesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;

        SurfaceDesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

        SurfaceDesc.dwWidth = BM.bmWidth;

        SurfaceDesc.dwHeight = BM.bmHeight;

        if (FHermes->FDirectDraw->CreateSurface(&SurfaceDesc, &Result, NULL) != DD_OK)

          throw Exception("CreateSurface failed");

        DDCopyBitmap(Result, Bitmap, 0, 0, 0, 0);

      }

      catch(Exception &E)

      {

        FHermes->ErrorEvent("TSpeedDraw.CreateSurface: " + E.Message);

      }

      return Result;

    }

    HANDLE __fastcall TDraw::GetDib(HANDLE Instance,  System::AnsiString S)

    {

      HANDLE Result;

      UINT Flags;

      if (Instance != 0)

        Flags = LR_CREATEDIBSECTION;

      else

        Flags = LR_LOADFROMFILE | LR_CREATEDIBSECTION;

      Result = LoadImage((HANDLE)Instance, S.c_str(), IMAGE_BITMAP,

                  0, 0, Flags);

      if (Result == 0)

        FHermes->ErrorEvent("TSpeedDraw.GetDib: Could not load bitmap");

      return Result;

    }

    IDirectDrawPalette* __fastcall TDraw::LoadPalette(HANDLE Instance,

        const System::AnsiString BitmapName)

    {

        IDirectDrawPalette* ddpal;

        int                 i;

        int                 n;

        int                 fh;

        HRSRC               h;

        LPBITMAPINFO        BitmapInfo;

        PALETTEENTRY        ape[256];

        RGBQUAD *           RGBQuad;

        //

        // build a 332 palette as the default.

        //

        for (i=0; i<256; i++)

        {

            ape[i].peRed   = (BYTE)(((i >> 5) & 0x07) * 255 / 7);

            ape[i].peGreen = (BYTE)(((i >> 2) & 0x07) * 255 / 7);

            ape[i].peBlue  = (BYTE)(((i >> 0) & 0x03) * 255 / 3);

            ape[i].peFlags = (BYTE)0;

        }

        if (BitmapName == "")

          FHermes->ErrorEvent("No bitmapname in LoadPalette");

        //

        // get a pointer to the bitmap resource.

        //

        if (Instance)

        {

          h = FindResource(Instance, BitmapName.c_str(), RT_BITMAP);

          if (h)

          {

            BitmapInfo = (LPBITMAPINFO)LockResource(LoadResource(Instance, h));

            if (!BitmapInfo)

              throw EDDError("No LockResouce: " + this->ClassName());

            RGBQuad = (RGBQUAD*)BitmapInfo->bmiColors;

            if (BitmapInfo == NULL || BitmapInfo->bmiHeader.biSize <sizeof(BITMAPINFOHEADER))

                n = 0;

            else if (BitmapInfo->bmiHeader.biBitCount > 8)

                n = 0;

            else if (BitmapInfo->bmiHeader.biClrUsed == 0)

                n = 1 << BitmapInfo->bmiHeader.biBitCount;

            else

                n = BitmapInfo->bmiHeader.biClrUsed;

            //

            //  a DIB color table has its colors stored BGR not RGB

            //  so flip them around.

            //

            for(i=0; i<n; i++ )

            {

              ape[i].peRed   = RGBQuad[i].rgbRed;

              ape[i].peGreen = RGBQuad[i].rgbGreen;

              ape[i].peBlue  = RGBQuad[i].rgbBlue;

              ape[i].peFlags = 0;

            }

          }

        }

        else

        {

          fh = _lopen(BitmapName.c_str(), OF_READ);

          if (fh != -1)

          {

            BITMAPFILEHEADER bf;

            BITMAPINFOHEADER bi;

           _lread(fh, &bf, sizeof(bf));

           _lread(fh, &bi, sizeof(bi));

           _lread(fh, ape, sizeof(ape));

           _lclose(fh);

            if (bi.biSize != sizeof(BITMAPINFOHEADER))

              n = 0;

            else if (bi.biBitCount > 8)

              n = 0;

            else if (bi.biClrUsed == 0)

              n = 1 << bi.biBitCount;

            else

              n = bi.biClrUsed;

            //

            //  a DIB color table has its colors stored BGR not RGB

            //  so flip them around.

            //

            for(i=0; i<n; i++ )

            {

                BYTE r = ape[i].peRed;

                ape[i].peRed  = ape[i].peBlue;

                ape[i].peBlue = r;

            }

          }

        }

        FHermes->FDirectDraw->CreatePalette(DDPCAPS_8BIT, ape, &ddpal, NULL);

        return ddpal;

    }

    void __fastcall TDraw::WriteXY(int X, int Y,  System::AnsiString S)

    {

      HDC DC;

      FHermes->BackSurface->GetDC(&DC);

      SetBkMode(DC, TRANSPARENT);

      TextOut(DC, X, Y, S.c_str(), S.Length());

      FHermes->BackSurface->ReleaseDC(DC);

    }

    // ------------------------

    // -- TScene --------------

    // ------------------------

    __fastcall TScene::TScene(TComponent *AOwner) : TDraw(AOwner)

    {

      ShowBitmap = true;

    }

    void __fastcall TScene::DestroyObjects(void)

    {

      if(FWorkSurface != NULL)

        FWorkSurface->Release();

      FWorkSurface = NULL;

    }

    long _fastcall TScene::RestoreSurfaces(void)

    {

      long Result;

      Result = FWorkSurface->Restore();

      if(Result == DD_OK)

        DDReloadBitmapLib(FLib, FWorkSurface, FBackgroundBitmap);

      return(Result);

    }

    void __fastcall TScene::SetupSurfaces(System::TObject *Sender)

    {

      AnsiString ErrStr = "TSpeedDraw.SetupWorkSurface: No Surface Desc %d %s";

      DDSURFACEDESC SurfaceDesc;

      HRESULT hr;

      FHermes = dynamic_cast <THermes *>(Sender);

      if(FDLLName != "")

      {

        if(!FLib)

          FLib = LoadLibrary(FDLLName.c_str());

        if(int(FLib) < 32)

        throw EDDError("No Library");

      }

      if(!CreateDDSurface(FWorkSurface, FBackgroundBitmap, True))

        throw EDDError("TSpeedDraw.SetupWorkSurface: No WorkSurface: " +

          FBackgroundBitmap);

         else

      {

        SurfaceDesc.dwSize = sizeof(SurfaceDesc);

        hr = FWorkSurface->GetSurfaceDesc(&SurfaceDesc);

        if(hr != DD_OK)

          throw EDDError(ErrStr, OPENARRAY(TVarRec, (int(hr), GetOleError(hr))));

        FBackRect = Rect(0, 0, SurfaceDesc.dwWidth, SurfaceDesc.dwHeight);

      }

      if(FOnSetupSurfaces)

      FOnSetupSurfaces(this);

    }

    void _fastcall TScene::DrawScene()

    {

      AnsiString ErrStr = "TSpeedDraw.BackGroundBlits: $%x \r %s \r %s";

      HRESULT hr;

      HDC DC;

      HBRUSH OldBrush, Brush;

      if (FBlankScene)

      {

        FHermes->BackSurface->GetDC(&DC); // Don't step through!!

        Brush = CreateSolidBrush(FBackColor);

        OldBrush = SelectObject(DC, Brush);

        Rectangle(DC, 0, 0, 640, 480);

        SelectObject(DC, OldBrush);

        DeleteObject(Brush);

        FHermes->BackSurface->ReleaseDC(DC);

      }

      if (FShowBitmap)

      {

        hr = AHermes->BackSurface->BltFast(FBackOrigin.x, FBackOrigin.y,

              FWorkSurface, &FBackRect,

              DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);

        if (!Windows::Succeeded(hr))

          throw EDDError(ErrStr, OPENARRAY(TVarRec,

            (int(hr), GetOleError(hr), "Check BackRect, BackOrigin???")));

      }

      if (FOnDrawScene)

        FOnDrawScene(this);

    }

    ///////////////////////////////////////

    // THermesTiler ///////////////////////

    ///////////////////////////////////////

    __fastcall THermesTiler::THermesTiler(Classes::TComponent* AOwner)

     :TScene(AOwner)

    {

      FBitmapWidth = 32;

      FBitmapHeight = 32;

      FMaxMapRows = 12;

      FMaxMapCols = 20;

    }

    __fastcall THermesTiler::~THermesTiler(void)

    {

      DestroyObjects();

    }

    void __fastcall THermesTiler::DrawScene(void)

    {

      TScene::DrawScene();

      int i, j, k;

      TSpecialRect SpR;

      if (FTileSurface == NULL)

        return;

      k = 1;

      for (j = 1; j <= FMaxMapRows; j++)

        for (i = 1; i <= FMaxMapCols; i++)

        {

          SpR = GetRect(i + FHermes->CreatureList->MapCol, j + FHermes->CreatureList->MapRow);

          FHermes->BackSurface->BltFast(FBitmapWidth * (k - 1),

            FBitmapHeight * (j - 1), FTileSurface, &SpR.R1, DDBLTFAST_SRCCOLORKEY);

          if (SpR.IsCreature) // There's a character or building here

            FHermes->BackSurface->BltFast(FBitmapWidth * (k - 1),

              FBitmapHeight * (j - 1), FTileSurface, &SpR.R2, DDBLTFAST_SRCCOLORKEY);

          if ((i % 20) == 0)

            k = 1;

          else

            k++;

        }

    }

    void __fastcall THermesTiler::SetupSurfaces(System::TObject* Sender)

    {

      TScene::SetupSurfaces(Sender);

      if (FTileMap != "")

        if (!CreateDDSurface(FTileSurface, FTileMap, True))

          throw Exception("THermesTiler.InitObjects");

    }

    void __fastcall THermesTiler::DestroyObjects(void)

    {

      if (FTileMap != "")

        if (FTileSurface != NULL)

          FTileSurface->Release();

      FTileSurface = NULL;

    }

    Windows::TRect __fastcall THermesTiler::MapTypeToTileRect(int MapType)

    {

      int X, Y = 0;

      if ((MapType > 19) && (MapType < 41))

      {

        Y = FBitmapHeight;

        X = (MapType - 21) * FBitmapWidth;

      }

      else if (MapType > 39)

      {

        Y = 64;

        X = (MapType - 41) * FBitmapWidth;

      }

      else

      {

        X = MapType * FBitmapWidth;

      }

      return Rect(X, Y, X + FBitmapWidth, FBitmapHeight + Y);

    }

    long __fastcall THermesTiler::RestoreSurfaces(void)

    {

      long Result = TScene::RestoreSurfaces();

      if (Result == DD_OK)

      {

        Result = FTileSurface->Restore();

        if (Result == DD_OK)

          if (FTileMap != "")

            DDReloadBitmapLib(FLib, FTileSurface, FTileMap);

      }

      return Result;

    }

    ///////////////////////////////////////

    // THermesChart ///////////////////////

    ///////////////////////////////////////

    bool __fastcall THermesChart::CheckHeroPos(Creatures1::TCreatureList* HeroList,

                                               int Col, int Row)

    {

      int NewPosType;

      TPoint NewPos;

      bool MoveOk = True;

      if (OnHeroMove != NULL)

      {

        NewPos.x = FHero->TrueCol + Col;

        NewPos.y = FHero->TrueRow + Row;

        NewPosType = HeroList->GetMapType(NewPos.x, NewPos.y);

        OnHeroMove(this, NewPos, NewPosType, MoveOk);

      }

      return MoveOk;

    }

    Windows::TRect __fastcall THermesChart::MapTypeToCreature(int Col, int Row)

    {

      TCreature *Creature = FHermes->CreatureList->CreatureFromLocation(Col, Row);

      if (Creature->TypeStr == "Hero")

        return MapTypeToTileRect(3);

      else

        return MapTypeToTileRect(StrToInt(Creature->ID));

    }

    TSpecialRect __fastcall THermesChart::GetRect(int Col, int Row)

    {

      Byte MapType;

      TSpecialRect Result;

      MapType = FHermes->CreatureList->Map[Col][Row];

      if ((FHermes->FTimerOdd == True) && (MapType == 1 /*mtWater*/))

        MapType = 4; // mtWater2;

      if (BitOn(7, MapType))

      {

        Result.IsCreature = True;

        SetBit(7, 0, MapType);

        Result.R1 = MapTypeToTileRect(MapType);

        Result.R2 = MapTypeToCreature(Col, Row);

      }

      else

      {

        Result.IsCreature = False;

        Result.R1 = MapTypeToTileRect(MapType);

      }

      return Result;

    }

    bool __fastcall THermesChart::MoveGrid(int Col, int Row, bool CallFlip)

    {

      TPoint P;

      if (CheckHeroPos(FHermes->CreatureList, Col, Row))

      {

        P.x = FHermes->CreatureList->MapCol + Col;

        P.y = FHermes->CreatureList->MapRow + Row;

        if ((P.x < 0) ||

            (P.x > (FHermes->CreatureList->MaxCols - FMaxMapCols)) ||

            (P.y < 0) ||

            (P.y > (FHermes->CreatureList->MaxRows - FMaxMapRows)))

          return False;

        FHermes->CreatureList->MapCol = P.x;

        FHermes->CreatureList->MapRow = P.y;

        FHermes->CreatureList->MoveCreature(FHero, Col, Row);

        FHermes->Flip();

        return True;

      }

      return False;

    }

    __fastcall THermesChart::~THermesChart(void)

    {

    }

    void __fastcall THermesChart::MoveHero(int NewCol, int NewRow)

    {

      TPoint P;

      if (!CheckHeroPos(FHermes->CreatureList, NewCol, NewRow))

        return;

      P.x = FHero->ScreenCol + NewCol;

      P.y = FHero->ScreenRow + NewRow;

      if ((P.x <= 0) || (P.x > (FMaxMapCols)) ||

         (P.y <= 1) || (P.y > FMaxMapRows))

        return;

      FHero->ScreenCol = P.x;

      FHero->ScreenRow = P.y;

      FHermes->CreatureList->MoveCreature(FHero, NewCol, NewRow);

      FHermes->Flip();

    }

    void __fastcall THermesChart::SetupSurfaces(System::TObject* Sender)

    {

      THermesTiler::SetupSurfaces(Sender);

      if (FHermes->CreatureList == NULL)

        throw EDDError("CreatureList cannot be blank.");

      FHeroActive = True;

      FHero = FHermes->CreatureList->CreatureFromName("Hero");

    }

    void __fastcall THermesChart::Move(int Value)

    {

      if (Value == VK_INSERT)

        HeroActive = !HeroActive;

      else if (HeroActive)

        switch (Value)

        {

          case VK_RIGHT: MoveHero(1, 0); break;

          case VK_LEFT: MoveHero(-1, 0); break;

          case VK_DOWN: MoveHero(0, 1); break;

          case VK_UP: MoveHero(0, -1); break;

        }

      else

        switch(Value)

        {

          case VK_RIGHT: MoveGrid(1, 0, False); break;

          case VK_LEFT: MoveGrid(-1, 0, False); break;

          case VK_DOWN: MoveGrid(0, 1, False); break;

          case VK_UP: MoveGrid(0, -1, False); break;

        }

    }

    /* ------------------------ */

    /* -- TSprite ------------- */

    /* ------------------------ */

    bool __fastcall TSprite::IsHit(int X, int Y)

    {

      X = X - XPos;

      Y = Y - YPos;

      if ((X >= 0) && (Y >= 0) && (X <= Rect.right) && (Y <= Rect.bottom))

        return true;

      else

        return false;

    }

    /* ------------------------ */

    /* -- TSpriteScene -------- */

    /* ------------------------ */

    __fastcall TSpriteScene::TSpriteScene(TComponent *AOwner)

      : TScene(AOwner)

    {

      FSpriteList = new TList();

    }

    __fastcall TSpriteScene::~TSpriteScene()

    {

      DestroyObjects();

        delete FSpriteList;

    }

    void __fastcall TSpriteScene::DestroyObjects(void)

    {

      if(FSpriteList != NULL)

      {

        for(int i = 0; i < FSpriteList->Count; i++)

          ((TSprite*)(FSpriteList->Items[i]))->Surface->Release();

        FSpriteList->Clear();

      }

    }

    void __fastcall TSpriteScene::AddSprite(TSprite *Sprite)

    {

      SpriteList->Add(Sprite);

    }

    void __fastcall TSpriteScene::DrawScene(void)

    {

      const char ErrStr[] = "TSpriteScene.DrawScene";

      HResult hr;

      TSprite *Sprite;

      TScene::DrawScene();

      for(int i = 0; i < FSpriteList->Count; i++)

      {

        Sprite = (TSprite *)FSpriteList->Items[i];

        hr = FHermes->BackSurface->BltFast(Sprite->XPos, Sprite->YPos,

          Sprite->Surface, &Sprite->FRect, DDBLTFAST_WAIT | DDBLTFAST_SRCCOLORKEY);

        if(!Windows::Succeeded(hr))

          throw(EDDError(ErrStr, OPENARRAY(TVarRec, (int(hr), GetOleError(hr),

            "Check BackRect, BackOrigin???"))));

      }

    }

    long __fastcall TSpriteScene::RestoreSurfaces(void)

    {

      TSprite *Sprite;

      HRESULT Result;

      Result = TScene::RestoreSurfaces();

      if(Result == DD_OK)

      {

            for(int i = 0; i < FSpriteList->Count; i++)

        {

          Sprite = (TSprite *)FSpriteList->Items[i];

          Result = Sprite->Surface->Restore();

          if(Result == DD_OK)

            DDReloadBitmapLib(FLib, Sprite->Surface, Sprite->Bitmap);

          else

            break; // Exit on error

        }

      }

      return((long)Result);

    }

    void __fastcall TSpriteScene::SetupSurfaces(TObject *Sender)

    {

      IDirectDrawSurface *Surface;

      TSprite *Sprite;

      DDSURFACEDESC SurfaceDesc;

      HRESULT hr;

      TScene::SetupSurfaces(Sender);

      if(SpriteList == NULL)

        return;

      for(int i = 0; i < SpriteList->Count; i++)

      {

        Sprite = (TSprite *)SpriteList->Items[i];

        if(!CreateDDSurface(Surface, Sprite->Bitmap, False))

          throw EDDError("Could not create surface");

        SurfaceDesc.dwSize = sizeof(SurfaceDesc);

        hr = Surface->GetSurfaceDesc(&SurfaceDesc);

        if(hr == DD_OK)

          Sprite->Rect = Rect(0, 0, SurfaceDesc.dwWidth, SurfaceDesc.dwHeight);

        else

          throw EDDError("No SurfaceDesc");

        Sprite->Surface = Surface;

      }

    }

    void __fastcall TSceneEditor::Edit(void)

    {

      RunSceneEditorDlg((TScene *)Component);

    }

    namespace Mercury2

    {

      void __fastcall Register()

      {

        TComponentClass classes[5] = {__classid(THermes), __classid(TScene),

          __classid(THermesChart), __classid(TSpriteScene), __classid(TSprite)};

        RegisterComponents("Unleash", classes, 4);

        RegisterComponentEditor(__classid(TScene), __classid(TSceneEditor));

      }
`}` ## Summary In this chapter, you learned how to create a simple graphics engine that gives you access to DirectX. In particular, you learned how to do the following: Get into and out of Exclusive mode Restore surfaces when the user switches away from a DirectX application Create backgrounds and sprites Work with a tiled world that expands far beyond the reaches of the screen In the next chapter, I add a simple game engine to this graphics engine and then give a simple example of how to use these tools to create the elements of a strategy game.
| ©Copyright, Macmillan Computer Publishing. All rights reserved.